aboutsummaryrefslogtreecommitdiffstats
path: root/packages
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-27 22:21:26 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-27 22:21:26 +0200
commita45e77df1b0024707914a446aa89d33baa223787 (patch)
tree969a4976c91d7aaa27b11ba212f99d69e2ccce0f /packages
parentc7899e2218af26f3bab6a157c70acf5eb7871da6 (diff)
downloadmeshbay-a45e77df1b0024707914a446aa89d33baa223787.tar.gz
test: make both suites pass on Windows
Most of these failed on Windows for reasons that had nothing to do with the code under test, which is how real Windows defects hid among them: - Read and write files as UTF-8, and talk to Node in UTF-8. read_text(), write_text() and subprocess text=True use the locale codepage, cp1252 on Windows: "é", "—" and "→" arrived as "?" or crashed, some sixty tests. Calls to PowerShell and schtasks are left alone -- they answer in the console codepage. - Import ESM harness modules by file URL (as_uri): a raw "C:\..." path is not a module specifier. - test_cli_golden: mask the tmp path in its JSON-escaped form, spell it the POSIX way, record on Linux, mask the protocol version (the recording had failed everywhere since the MNP 4.0 bump) and argparse's version-dependent quoting; point USERPROFILE at the tmp home, or `member invite` and `operator pair` wrote their codes into the developer's profile. - test_disk_io_off_loop: expect what a free loop can reach on the platform's timer, 15.6 ms on Windows, not an assumed 5 ms. - test_root_paths_are_operator_only: expect the OS's spelling of the path. - test_audio_meta_cache: find ffprobe with shutil.which. Node suite on Windows: 1489 passed, none failed. Hub suite: 3 failures left, all older than this change (two SQLite concurrency tests, one transfer resume). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages')
-rw-r--r--packages/meshbay-hub/tests/harness/layout_probe.py2
-rw-r--r--packages/meshbay-hub/tests/harness/scroll_probe.py2
-rw-r--r--packages/meshbay-hub/tests/test_account_pinning.py4
-rw-r--r--packages/meshbay-hub/tests/test_argon2_off_loop.py2
-rw-r--r--packages/meshbay-hub/tests/test_asset_versioning.py2
-rw-r--r--packages/meshbay-hub/tests/test_availability_between_members.py2
-rw-r--r--packages/meshbay-hub/tests/test_browser_idle_signout.py2
-rw-r--r--packages/meshbay-hub/tests/test_bundle_kdf_parity.py8
-rw-r--r--packages/meshbay-hub/tests/test_captcha_host_check.py9
-rw-r--r--packages/meshbay-hub/tests/test_cast_subtitles.py6
-rw-r--r--packages/meshbay-hub/tests/test_challenge_signature_client.py6
-rw-r--r--packages/meshbay-hub/tests/test_chat_scroll_bottom.py2
-rw-r--r--packages/meshbay-hub/tests/test_client_version_gate.py8
-rw-r--r--packages/meshbay-hub/tests/test_downloads.py54
-rw-r--r--packages/meshbay-hub/tests/test_files_drop_upload.py2
-rw-r--r--packages/meshbay-hub/tests/test_files_sorting.py2
-rw-r--r--packages/meshbay-hub/tests/test_hook_ordering.py6
-rw-r--r--packages/meshbay-hub/tests/test_index_seal_client.py4
-rw-r--r--packages/meshbay-hub/tests/test_indexing_dock.py8
-rw-r--r--packages/meshbay-hub/tests/test_invite_link_client.py4
-rw-r--r--packages/meshbay-hub/tests/test_layout_measured.py4
-rw-r--r--packages/meshbay-hub/tests/test_layout_responsive.py4
-rw-r--r--packages/meshbay-hub/tests/test_locales.py11
-rw-r--r--packages/meshbay-hub/tests/test_media_pager.py4
-rw-r--r--packages/meshbay-hub/tests/test_member_removal.py2
-rw-r--r--packages/meshbay-hub/tests/test_memory_ceiling.py8
-rw-r--r--packages/meshbay-hub/tests/test_music_queue.py2
-rw-r--r--packages/meshbay-hub/tests/test_node_page_width_measured.py2
-rw-r--r--packages/meshbay-hub/tests/test_notification_dismissal.py4
-rw-r--r--packages/meshbay-hub/tests/test_offer_retry.py2
-rw-r--r--packages/meshbay-hub/tests/test_playlist_crypto.py6
-rw-r--r--packages/meshbay-hub/tests/test_playlist_key.py6
-rw-r--r--packages/meshbay-hub/tests/test_playlist_merge.py6
-rw-r--r--packages/meshbay-hub/tests/test_queue_ops.py6
-rw-r--r--packages/meshbay-hub/tests/test_reconnect_refresh.py12
-rw-r--r--packages/meshbay-hub/tests/test_recovery_key.py4
-rw-r--r--packages/meshbay-hub/tests/test_resume_position.py2
-rw-r--r--packages/meshbay-hub/tests/test_rewrap_fanout.py4
-rw-r--r--packages/meshbay-hub/tests/test_search_connect_deadline.py2
-rw-r--r--packages/meshbay-hub/tests/test_search_fanout.py2
-rw-r--r--packages/meshbay-hub/tests/test_search_files_unmerged.py2
-rw-r--r--packages/meshbay-hub/tests/test_search_media_merge.py10
-rw-r--r--packages/meshbay-hub/tests/test_search_pool.py2
-rw-r--r--packages/meshbay-hub/tests/test_search_source_merge.py6
-rw-r--r--packages/meshbay-hub/tests/test_season_panel_placement.py10
-rw-r--r--packages/meshbay-hub/tests/test_session_renewal.py14
-rw-r--r--packages/meshbay-hub/tests/test_site_basics.py2
-rw-r--r--packages/meshbay-hub/tests/test_spa_ordering.py12
-rw-r--r--packages/meshbay-hub/tests/test_spa_syntax.py6
-rw-r--r--packages/meshbay-hub/tests/test_streamed_download_reliability.py14
-rw-r--r--packages/meshbay-hub/tests/test_table_rows_measured.py2
-rw-r--r--packages/meshbay-hub/tests/test_transfers.py34
-rw-r--r--packages/meshbay-hub/tests/test_transport_contracts.py12
-rw-r--r--packages/meshbay-hub/tests/test_upload_seal_client.py4
-rw-r--r--packages/meshbay-hub/tests/test_versions_agree.py6
-rw-r--r--packages/meshbay-hub/tests/test_video_audio_track.py6
-rw-r--r--packages/meshbay-hub/tests/test_video_buffer_ceiling.py6
-rw-r--r--packages/meshbay-hub/tests/test_video_default_season.py6
-rw-r--r--packages/meshbay-hub/tests/test_video_detail_measured.py4
-rw-r--r--packages/meshbay-hub/tests/test_video_reconnect.py4
-rw-r--r--packages/meshbay-hub/tests/test_video_seek.py12
-rw-r--r--packages/meshbay-hub/tests/test_video_stream_switch.py6
-rw-r--r--packages/meshbay-hub/tests/test_video_subtitles.py6
-rw-r--r--packages/meshbay-hub/tests/test_zip_size_limit.py6
-rw-r--r--packages/meshbay-hub/tests/test_zipstream.py16
-rw-r--r--packages/meshbay-node/tests/golden/cli.json4
-rw-r--r--packages/meshbay-node/tests/test_added_root_does_not_wait_for_its_scan.py2
-rw-r--r--packages/meshbay-node/tests/test_audio_meta_cache.py6
-rw-r--r--packages/meshbay-node/tests/test_cli_dispatch.py2
-rw-r--r--packages/meshbay-node/tests/test_cli_golden.py22
-rw-r--r--packages/meshbay-node/tests/test_disk_io_off_loop.py15
-rw-r--r--packages/meshbay-node/tests/test_hot_reload_survives_client_close.py8
-rw-r--r--packages/meshbay-node/tests/test_keystore.py2
-rw-r--r--packages/meshbay-node/tests/test_leaseless_reads.py2
-rw-r--r--packages/meshbay-node/tests/test_node_status.py20
-rw-r--r--packages/meshbay-node/tests/test_ops.py2
-rw-r--r--packages/meshbay-node/tests/test_quic_enabled.py2
-rw-r--r--packages/meshbay-node/tests/test_root_ops_reach_the_live_set.py12
-rw-r--r--packages/meshbay-node/tests/test_root_paths_are_operator_only.py4
-rw-r--r--packages/meshbay-node/tests/test_root_work_outlives_the_session.py2
-rw-r--r--packages/meshbay-node/tests/test_roots.py6
-rw-r--r--packages/meshbay-node/tests/test_roster_pairing.py6
-rw-r--r--packages/meshbay-node/tests/test_security_regressions.py2
-rw-r--r--packages/meshbay-node/tests/test_stream_capacity_config.py2
-rw-r--r--packages/meshbay-node/tests/test_upload_size_cap.py2
85 files changed, 293 insertions, 266 deletions
diff --git a/packages/meshbay-hub/tests/harness/layout_probe.py b/packages/meshbay-hub/tests/harness/layout_probe.py
index 65b3083..0abbda6 100644
--- a/packages/meshbay-hub/tests/harness/layout_probe.py
+++ b/packages/meshbay-hub/tests/harness/layout_probe.py
@@ -81,7 +81,7 @@ RECORDS = []
def main() -> int:
widths = [int(w) for w in sys.argv[1].split(",")]
- fragment = Path(sys.argv[2]).read_text()
+ fragment = Path(sys.argv[2]).read_text(encoding="utf-8")
selectors = sys.argv[3:]
class H(http.server.BaseHTTPRequestHandler):
diff --git a/packages/meshbay-hub/tests/harness/scroll_probe.py b/packages/meshbay-hub/tests/harness/scroll_probe.py
index ae407b8..55d5b2b 100644
--- a/packages/meshbay-hub/tests/harness/scroll_probe.py
+++ b/packages/meshbay-hub/tests/harness/scroll_probe.py
@@ -32,7 +32,7 @@ STATIC = Path(__file__).resolve().parents[2] / "src" / "meshbay_hub" / "static"
# group-page refactor.
APP = STATIC / "chat-app.js"
PORT = 8736
-FRAG = Path(sys.argv[1]).read_text()
+FRAG = Path(sys.argv[1]).read_text(encoding="utf-8")
HEIGHTS = ([int(h) for h in sys.argv[2].split(",")]
if len(sys.argv) > 2 else [700, 900, 1200])
diff --git a/packages/meshbay-hub/tests/test_account_pinning.py b/packages/meshbay-hub/tests/test_account_pinning.py
index 529ebd5..ebd29bd 100644
--- a/packages/meshbay-hub/tests/test_account_pinning.py
+++ b/packages/meshbay-hub/tests/test_account_pinning.py
@@ -102,11 +102,11 @@ def _entry(user, pk_ed, pk_x="cGtY", *, added_by="", sk_signer=None,
def _verify(devices, node_pk=NODE_PK):
with tempfile.TemporaryDirectory() as tmp:
h = Path(tmp) / "h.js"
- h.write_text(_HARNESS)
+ h.write_text(_HARNESS, encoding="utf-8")
payload = Path(tmp) / "in.json"
payload.write_text(json.dumps(
{"payload": {"devices": devices, "node_pk": node_pk},
- "node_pk": node_pk}))
+ "node_pk": node_pk}), encoding="utf-8")
run = subprocess.run(
["node", str(h), str(CRYPTO), transport_argv(), str(payload)],
capture_output=True, timeout=60)
diff --git a/packages/meshbay-hub/tests/test_argon2_off_loop.py b/packages/meshbay-hub/tests/test_argon2_off_loop.py
index 5c25a8e..ae6cc08 100644
--- a/packages/meshbay-hub/tests/test_argon2_off_loop.py
+++ b/packages/meshbay-hub/tests/test_argon2_off_loop.py
@@ -28,7 +28,7 @@ def test_nothing_derives_argon2_on_the_event_loop():
for path in SRC.rglob("*.py"):
if path.name == "auth.py":
continue
- for n, line in enumerate(path.read_text().splitlines(), 1):
+ for n, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
if direct.search(line):
offenders.append(f"{path.relative_to(SRC)}:{n}: {line.strip()}")
assert not offenders, (
diff --git a/packages/meshbay-hub/tests/test_asset_versioning.py b/packages/meshbay-hub/tests/test_asset_versioning.py
index aa2dc6d..7057311 100644
--- a/packages/meshbay-hub/tests/test_asset_versioning.py
+++ b/packages/meshbay-hub/tests/test_asset_versioning.py
@@ -135,7 +135,7 @@ def test_a_new_file_moves_the_fingerprint():
before = _asset_version()
extra = STATIC_DIR / "locales" / "zz-test-only.js"
try:
- extra.write_text("export default {};\n")
+ extra.write_text("export default {};\n", encoding="utf-8")
assert _asset_version() != before
finally:
extra.unlink()
diff --git a/packages/meshbay-hub/tests/test_availability_between_members.py b/packages/meshbay-hub/tests/test_availability_between_members.py
index cd10046..24d85a0 100644
--- a/packages/meshbay-hub/tests/test_availability_between_members.py
+++ b/packages/meshbay-hub/tests/test_availability_between_members.py
@@ -537,7 +537,7 @@ def test_no_mail_is_sent_from_the_event_loop():
for path in root.rglob("*.py"):
if path.name == "mail.py":
continue
- for n, line in enumerate(path.read_text().splitlines(), 1):
+ for n, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
if direct_call.search(line):
offenders.append(f"{path.name}:{n}: {line.strip()}")
assert not offenders, (
diff --git a/packages/meshbay-hub/tests/test_browser_idle_signout.py b/packages/meshbay-hub/tests/test_browser_idle_signout.py
index 50f7f04..454874a 100644
--- a/packages/meshbay-hub/tests/test_browser_idle_signout.py
+++ b/packages/meshbay-hub/tests/test_browser_idle_signout.py
@@ -84,7 +84,7 @@ def outcome():
pytest.skip("node is not available")
proc = subprocess.run(
[NODE, "--input-type=module", "--eval", HARNESS, IDLE.as_uri()],
- capture_output=True, text=True, timeout=30)
+ capture_output=True, text=True, encoding="utf-8", timeout=30)
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout.strip().splitlines()[-1])
diff --git a/packages/meshbay-hub/tests/test_bundle_kdf_parity.py b/packages/meshbay-hub/tests/test_bundle_kdf_parity.py
index 27e10d4..c62aace 100644
--- a/packages/meshbay-hub/tests/test_bundle_kdf_parity.py
+++ b/packages/meshbay-hub/tests/test_bundle_kdf_parity.py
@@ -77,19 +77,19 @@ const argon2 = require(process.argv[3]);
def js_hashes(tmp_path_factory):
d = tmp_path_factory.mktemp("kdf")
harness = d / "harness.cjs"
- harness.write_text(_HARNESS)
+ harness.write_text(_HARNESS, encoding="utf-8")
vectors = [
{"username": u, "password": p,
"mem": MEM_KIB, "time": TIME_COST, "lanes": LANES}
for u in CASES for p in PASSWORDS
]
payload = d / "vectors.json"
- payload.write_text(json.dumps(vectors))
+ payload.write_text(json.dumps(vectors), encoding="utf-8")
proc = subprocess.run(
["node", str(harness), str(VENDOR / "argon2.wasm"),
str(VENDOR / "argon2.min.js"), str(payload)],
- capture_output=True, text=True, timeout=300,
+ capture_output=True, text=True, encoding="utf-8", timeout=300,
)
if proc.returncode != 0:
pytest.fail(f"node harness failed:\n{proc.stderr[-2000:]}")
@@ -124,7 +124,7 @@ def test_parameters_still_match_the_client():
The numbers live in keyderive.js; this test is the second copy. Tuning one
without the other orphans every bundle already written, so make it fail.
"""
- source = (STATIC / "keyderive.js").read_text()
+ source = (STATIC / "keyderive.js").read_text(encoding="utf-8")
assert f"ARGON2_MEM_KIB = {MEM_KIB}" in source
assert f"ARGON2_TIME = {TIME_COST}" in source
assert f"ARGON2_LANES = {LANES}" in source
diff --git a/packages/meshbay-hub/tests/test_captcha_host_check.py b/packages/meshbay-hub/tests/test_captcha_host_check.py
index 778009f..a0ff509 100644
--- a/packages/meshbay-hub/tests/test_captcha_host_check.py
+++ b/packages/meshbay-hub/tests/test_captcha_host_check.py
@@ -208,7 +208,7 @@ def test_hub_toml_carries_the_allowed_hosts(tmp_path):
'[captcha]\n'
'site_key = "6Lsite"\n'
'secret_key = "6Lsecret"\n'
- 'allowed_hosts = ["meshbay.org", " meshbay ", "", "localhost"]\n')
+ 'allowed_hosts = ["meshbay.org", " meshbay ", "", "localhost"]\n', encoding="utf-8")
cfg = load_config(cfg_file)
@@ -222,7 +222,8 @@ def test_a_hub_toml_without_the_key_checks_nothing(tmp_path):
"""The upgrade path. A hub that never heard of this setting keeps the
behaviour it has, with reCAPTCHA doing the origin check."""
cfg_file = tmp_path / "hub.toml"
- cfg_file.write_text('[captcha]\nsite_key = "6Lsite"\nsecret_key = "6Lsecret"\n')
+ cfg_file.write_text('[captcha]\nsite_key = "6Lsite"\nsecret_key = "6Lsecret"\n',
+ encoding="utf-8")
assert load_config(cfg_file).captcha.host_check is None
@@ -231,10 +232,10 @@ def test_the_unattributed_flag_comes_from_the_config(tmp_path, monkeypatch):
cfg_file = tmp_path / "hub.toml"
cfg_file.write_text(
'[captcha]\nsite_key = "k"\nsecret_key = "s"\n'
- 'allowed_hosts = ["meshbay.org"]\nallow_unattributed_host = true\n')
+ 'allowed_hosts = ["meshbay.org"]\nallow_unattributed_host = true\n', encoding="utf-8")
assert load_config(cfg_file).captcha.allow_unattributed_host is True
- cfg_file.write_text('[captcha]\nsite_key = "k"\nsecret_key = "s"\n')
+ cfg_file.write_text('[captcha]\nsite_key = "k"\nsecret_key = "s"\n', encoding="utf-8")
assert load_config(cfg_file).captcha.allow_unattributed_host is False, (
"the default has to stay off — it is the looser of the two")
diff --git a/packages/meshbay-hub/tests/test_cast_subtitles.py b/packages/meshbay-hub/tests/test_cast_subtitles.py
index fdf7fcc..bdc279b 100644
--- a/packages/meshbay-hub/tests/test_cast_subtitles.py
+++ b/packages/meshbay-hub/tests/test_cast_subtitles.py
@@ -66,7 +66,7 @@ def _run(tmp_path, body: str, *args: str):
+ "\n" + body, encoding="utf-8")
proc = subprocess.run(
["node", str(script), *args],
- capture_output=True, text=True, timeout=30)
+ capture_output=True, text=True, encoding="utf-8", timeout=30)
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
@@ -260,7 +260,7 @@ def served(tmp_path_factory):
script.write_text(RELAY_SCRIPT, encoding="utf-8")
proc = subprocess.run(
["node", str(script), str(RELAY)],
- capture_output=True, text=True, timeout=60)
+ capture_output=True, text=True, encoding="utf-8", timeout=60)
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout.strip().splitlines()[-1])
@@ -363,7 +363,7 @@ def loaded(tmp_path_factory):
script.write_text(CHROMECAST_SCRIPT, encoding="utf-8")
proc = subprocess.run(
["node", str(script), str(CHROMECAST)],
- capture_output=True, text=True, timeout=60)
+ capture_output=True, text=True, encoding="utf-8", timeout=60)
if proc.returncode != 0:
pytest.skip(f"cast-chromecast.js is not loadable here: {proc.stderr}")
return json.loads(proc.stdout)
diff --git a/packages/meshbay-hub/tests/test_challenge_signature_client.py b/packages/meshbay-hub/tests/test_challenge_signature_client.py
index e3c33ac..b904698 100644
--- a/packages/meshbay-hub/tests/test_challenge_signature_client.py
+++ b/packages/meshbay-hub/tests/test_challenge_signature_client.py
@@ -93,11 +93,11 @@ def test_the_browser_holds_the_node_to_its_challenge(tmp_path):
"garbage for a signature": (case(sig=b"\x00" * 64), "refused"),
}
harness = tmp_path / "harness.js"
- harness.write_text(_HARNESS)
+ harness.write_text(_HARNESS, encoding="utf-8")
payload = tmp_path / "cases.json"
- payload.write_text(json.dumps([c for c, _ in cases.values()]))
+ payload.write_text(json.dumps([c for c, _ in cases.values()]), encoding="utf-8")
proc = subprocess.run(["node", str(harness), str(STATIC), str(payload), transport_argv()],
- capture_output=True, text=True, timeout=60)
+ capture_output=True, text=True, encoding="utf-8", timeout=60)
assert proc.returncode == 0, proc.stderr
got = dict(zip(cases, json.loads(proc.stdout)))
assert got == {name: want for name, (_, want) in cases.items()}
diff --git a/packages/meshbay-hub/tests/test_chat_scroll_bottom.py b/packages/meshbay-hub/tests/test_chat_scroll_bottom.py
index 7b66c00..42b1055 100644
--- a/packages/meshbay-hub/tests/test_chat_scroll_bottom.py
+++ b/packages/meshbay-hub/tests/test_chat_scroll_bottom.py
@@ -42,7 +42,7 @@ pytestmark = pytest.mark.skipif(not CHAT.exists(), reason="SPA sources not prese
def _chat_panel_source() -> str:
- src = CHAT.read_text()
+ src = CHAT.read_text(encoding="utf-8")
start = src.index("\nfunction ChatPanel(")
end = src.find("\nfunction ", start + 1)
return src[start:end if end != -1 else len(src)]
diff --git a/packages/meshbay-hub/tests/test_client_version_gate.py b/packages/meshbay-hub/tests/test_client_version_gate.py
index 4dc9b98..91cb99a 100644
--- a/packages/meshbay-hub/tests/test_client_version_gate.py
+++ b/packages/meshbay-hub/tests/test_client_version_gate.py
@@ -32,7 +32,7 @@ pytestmark = pytest.mark.skipif(
def _lift(name: str) -> str:
- src = MAIN.read_text()
+ src = MAIN.read_text(encoding="utf-8")
cut = src[src.index(name):]
return cut[:cut.index("\n}\n") + 2]
@@ -69,8 +69,8 @@ out.compare = [
compareVersions('nonsense', '1.1.0'),
];
console.log(JSON.stringify(out));
-""")
- proc = subprocess.run(["node", str(script)], capture_output=True, text=True)
+""", encoding="utf-8")
+ proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8")
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
@@ -133,7 +133,7 @@ def test_a_first_run_with_no_hub_yet_is_not_stopped(tmp_path):
def test_the_gate_runs_before_the_window_is_built():
"""A window that opens and then cannot connect is the failure this
replaces, so the order is the whole point."""
- src = MAIN.read_text()
+ src = MAIN.read_text(encoding="utf-8")
ready = src[src.index("app.whenReady().then("):]
ready = ready[:ready.index("createWindow();")]
assert "await refuseIfTooOld()" in ready, (
diff --git a/packages/meshbay-hub/tests/test_downloads.py b/packages/meshbay-hub/tests/test_downloads.py
index 395053b..c8c68a9 100644
--- a/packages/meshbay-hub/tests/test_downloads.py
+++ b/packages/meshbay-hub/tests/test_downloads.py
@@ -26,7 +26,7 @@ pytestmark = pytest.mark.skipif(
def _run(body, tmp_path):
module = tmp_path / "downloads.mjs"
- module.write_text(DOWNLOADS.read_text())
+ module.write_text(DOWNLOADS.read_text(encoding="utf-8"), encoding="utf-8")
script = tmp_path / "case.mjs"
script.write_text(
# A localStorage good enough for a preference, so the module can be
@@ -36,12 +36,12 @@ def _run(body, tmp_path):
" getItem: k => (store.has(k) ? store.get(k) : null),\n"
" setItem: (k, v) => store.set(k, String(v)),\n"
"};\n"
- f"const M = await import('{module.as_posix()}');\n"
+ f"const M = await import('{module.as_uri()}');\n"
"const out = [];\n"
"const say = (...a) => out.push(...a);\n"
f"{body}\n"
- "console.log(JSON.stringify(out));\n")
- proc = subprocess.run(["node", str(script)], capture_output=True, text=True)
+ "console.log(JSON.stringify(out));\n", encoding="utf-8")
+ proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8")
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
@@ -104,7 +104,7 @@ def test_the_open_action_reads_the_file_back(tmp_path):
manager either. It is only offered for a file written into a granted folder,
since that is the one a page can read back.
"""
- src = DOWNLOADS.read_text()
+ src = DOWNLOADS.read_text(encoding="utf-8")
target = src[src.index("export async function openTarget"):]
assert "getFile()" in target and "window.open(" in target
assert "revokeObjectURL" in target, "the blob URL must not be leaked"
@@ -122,7 +122,7 @@ def test_the_worker_only_answers_its_own_urls():
service worker that answers more than it should is a cache bug waiting to
happen.
"""
- src = SW.read_text()
+ src = SW.read_text(encoding="utf-8")
assert "startsWith(PREFIX)" in src
assert "self.location.origin" in src, "cross-origin requests must fall through"
# The API, not the word: the file explains in prose that it caches nothing.
@@ -131,7 +131,7 @@ def test_the_worker_only_answers_its_own_urls():
def test_the_download_is_announced_as_an_attachment():
- src = SW.read_text()
+ src = SW.read_text(encoding="utf-8")
assert "Content-Disposition" in src and "attachment" in src
assert "filename*=UTF-8''" in src, "a name with accents would be mangled"
assert "Content-Length" in src
@@ -142,7 +142,7 @@ def test_a_length_is_only_promised_when_it_is_known(tmp_path):
An archive is assembled as it goes and is larger than the files in it.
Announcing the sum of their sizes would truncate the download at that mark.
"""
- src = SW.read_text()
+ src = SW.read_text(encoding="utf-8")
assert "if (entry.size > 0)" in src
# The zip-directory download started in files-app.js (group-page refactor)
@@ -153,7 +153,7 @@ def test_a_length_is_only_promised_when_it_is_known(tmp_path):
# became a bare `target = ...` inside a try when _openDownloadTarget gained
# the ability to refuse an oversized download (test_memory_ceiling.py).
# What this test is about -- the `0` -- did not move.
- app = (STATIC / "file-utils.js").read_text()
+ app = (STATIC / "file-utils.js").read_text(encoding="utf-8")
# Anchored on the argument list, not on the function name: the call became
# `_openTargetInTurn(suggested, …)` when target openings were serialised.
# The `0` this test is about did not move.
@@ -169,7 +169,7 @@ def test_backpressure_is_real(tmp_path):
is transferred gives `writer.write()` something to wait on; posting chunks
to a port would queue them in memory and look identical from here.
"""
- src = DOWNLOADS.read_text()
+ src = DOWNLOADS.read_text(encoding="utf-8")
fn = src[src.index("export async function openStreamedDownload"):]
assert "new TransformStream()" in fn
# The transfer list may carry more than the stream — a reply port rides
@@ -210,13 +210,13 @@ def test_the_streamed_path_gives_up_rather_than_blocking_for_ever():
So the worker confirms that it actually answered, and this path reports
failure instead of returning a sink nobody drains.
"""
- src = DOWNLOADS.read_text()
+ src = DOWNLOADS.read_text(encoding="utf-8")
fn = src[src.index("export async function openStreamedDownload"):]
assert "mbdl-serving" in fn, "the worker has to confirm it served the request"
assert "Promise.race" in fn, "the confirmation needs a deadline"
assert "writable.abort" in fn, "give up cleanly so the caller can fall back"
- sw = (DOWNLOADS.parent / "sw.js").read_text()
+ sw = (DOWNLOADS.parent / "sw.js").read_text(encoding="utf-8")
assert "mbdl-serving" in sw, "and the worker has to send that confirmation"
@@ -227,7 +227,7 @@ def test_an_uncontrolled_page_is_not_treated_as_ready():
# became a parameter, and `serviceWorker()` no longer contains the words.
# The behaviour itself is executed in test_streamed_download_reliability.py;
# this stays as the cheap guard on the module's shape.
- src = DOWNLOADS.read_text()
+ src = DOWNLOADS.read_text(encoding="utf-8")
section = src[src.index("// ── Streaming to disk"):]
assert "navigator.serviceWorker.controller" in section
assert "controllerchange" in section, (
@@ -251,7 +251,7 @@ def test_an_apostrophe_in_a_name_does_not_lose_the_name(tmp_path):
The real function is lifted out of sw.js and run — a second copy here would
have the same blind spot as the first.
"""
- src = SW.read_text()
+ src = SW.read_text(encoding="utf-8")
fn = src[src.index("function contentDisposition"):]
fn = fn[:fn.index("\n}") + 2]
@@ -263,8 +263,8 @@ for (const name of ["S03E02. Queen's Landing.mp4", 'Caf\\u00e9 (2019).mkv',
out[name] = contentDisposition(name);
}
console.log(JSON.stringify(out));
-""")
- proc = subprocess.run(["node", str(script)], capture_output=True, text=True)
+""", encoding="utf-8")
+ proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8")
assert proc.returncode == 0, proc.stderr
out = json.loads(proc.stdout)
@@ -304,7 +304,7 @@ def test_a_sink_that_stops_consuming_fails_instead_of_hanging(tmp_path):
wrong. Bounding it does not fix whatever stopped the sink — it turns an
unexplainable freeze into a failed transfer that names itself.
"""
- src = (STATIC / "file-utils.js").read_text()
+ src = (STATIC / "file-utils.js").read_text(encoding="utf-8")
fn = src[src.index("async function _writeOrStall"):]
fn = fn[:fn.index("\n}\n") + 2]
@@ -328,8 +328,8 @@ const live = { write: async () => {} };
await _writeOrStall(live, new Uint8Array(4), 0);
out.liveOk = true;
console.log(JSON.stringify(out));
-""")
- proc = subprocess.run(["node", str(script)], capture_output=True, text=True)
+""", encoding="utf-8")
+ proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8")
assert proc.returncode == 0, proc.stderr
out = json.loads(proc.stdout)
assert out["threw"], "a dead sink hung for ever instead of failing"
@@ -358,8 +358,8 @@ def test_the_worker_is_kept_alive_while_it_streams():
clock. What it protects is that the ping exists at all, is cleared on both
exits, and is answered by the worker.
"""
- dl = DOWNLOADS.read_text()
- sw = SW.read_text()
+ dl = DOWNLOADS.read_text(encoding="utf-8")
+ sw = SW.read_text(encoding="utf-8")
assert "SW_KEEPALIVE_MS" in dl and "mbdl-ping" in dl, (
"nothing keeps the worker alive; downloads longer than ~30 s will "
@@ -389,7 +389,7 @@ def _turn_harness(tmp_path, name, body, *, picker=True, budget_ms=90000):
the budget is supplied here, so a case about the budget need not wait a
minute and a half for it.
"""
- src = (STATIC / "file-utils.js").read_text()
+ src = (STATIC / "file-utils.js").read_text(encoding="utf-8")
def lift(decl):
cut = src[src.index(decl):]
@@ -422,8 +422,8 @@ const TARGET_QUEUE_BUDGET_MS = {budget_ms};
""" + lift("function _openTargetInTurn") + lift("function _waitBriefly") + f"""
{body}
console.log(JSON.stringify(out));
-""")
- proc = subprocess.run(["node", str(script)], capture_output=True, text=True)
+""", encoding="utf-8")
+ proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8")
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
@@ -518,7 +518,7 @@ def test_a_pause_falls_between_chunks_and_resumes_at_one(tmp_path):
rule this repo follows for the video player: model the environment, never
the code under test.
"""
- src = (STATIC / "file-utils.js").read_text()
+ src = (STATIC / "file-utils.js").read_text(encoding="utf-8")
fn = src[src.index("async function pipelinedDownload"):]
fn = fn[:fn.index("\n}\n") + 2]
@@ -558,8 +558,8 @@ await pipelinedDownload({}, 'k', 'file', 10, () => {}, {}, signal, '',
out.resumeFrom);
out.writtenAfterResume = written.slice();
console.log(JSON.stringify(out));
-""")
- proc = subprocess.run(["node", str(script)], capture_output=True, text=True)
+""", encoding="utf-8")
+ proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8")
assert proc.returncode == 0, proc.stderr
out = json.loads(proc.stdout)
diff --git a/packages/meshbay-hub/tests/test_files_drop_upload.py b/packages/meshbay-hub/tests/test_files_drop_upload.py
index fc15c47..aa39f30 100644
--- a/packages/meshbay-hub/tests/test_files_drop_upload.py
+++ b/packages/meshbay-hub/tests/test_files_drop_upload.py
@@ -41,7 +41,7 @@ def source():
def _run(tmp_path, source, expr):
script = tmp_path / "case.js"
script.write_text(f"{source}\nconsole.log(JSON.stringify({expr}));", encoding="utf-8")
- out = subprocess.run(["node", str(script)], capture_output=True, text=True, check=True)
+ out = subprocess.run(["node", str(script)], capture_output=True, encoding="utf-8", check=True)
return json.loads(out.stdout)
diff --git a/packages/meshbay-hub/tests/test_files_sorting.py b/packages/meshbay-hub/tests/test_files_sorting.py
index 730d1e8..f3aaf34 100644
--- a/packages/meshbay-hub/tests/test_files_sorting.py
+++ b/packages/meshbay-hub/tests/test_files_sorting.py
@@ -38,7 +38,7 @@ def _names(tmp_path, source, rows, key, asc):
f"{source}\nconsole.log(JSON.stringify("
f"sortRows({json.dumps(rows)}, {json.dumps(key)}, {json.dumps(asc)}).map((r) => r.name)));",
encoding="utf-8")
- out = subprocess.run(["node", str(script)], capture_output=True, text=True, check=True)
+ out = subprocess.run(["node", str(script)], capture_output=True, encoding="utf-8", check=True)
return json.loads(out.stdout)
diff --git a/packages/meshbay-hub/tests/test_hook_ordering.py b/packages/meshbay-hub/tests/test_hook_ordering.py
index d5ffcfe..0172127 100644
--- a/packages/meshbay-hub/tests/test_hook_ordering.py
+++ b/packages/meshbay-hub/tests/test_hook_ordering.py
@@ -67,7 +67,7 @@ DEPS = re.compile(r"^ \}, \[([^\]]*)\]\);", re.M)
@pytest.fixture(scope="module")
def app():
- return APP.read_text()
+ return APP.read_text(encoding="utf-8")
def _components(src: str):
@@ -84,7 +84,7 @@ def _all_components():
path = STATIC / name
if not path.exists():
continue
- for cname, body in _components(path.read_text()):
+ for cname, body in _components(path.read_text(encoding="utf-8")):
yield f"{name}:{cname}", body
@@ -147,7 +147,7 @@ def test_the_mse_harness_reads_functions_in_source_order():
running it, which is the one class of defect it would otherwise be well
placed to catch.
"""
- harness = (Path(__file__).parent / "harness" / "mse_harness.mjs").read_text()
+ harness = (Path(__file__).parent / "harness" / "mse_harness.mjs").read_text(encoding="utf-8")
assert "sort" in harness and "indexOf" in harness, (
"the harness still extracts the player functions in a hardcoded order, "
"so it cannot see one declared before its own dependency")
diff --git a/packages/meshbay-hub/tests/test_index_seal_client.py b/packages/meshbay-hub/tests/test_index_seal_client.py
index 20f89d1..f12d791 100644
--- a/packages/meshbay-hub/tests/test_index_seal_client.py
+++ b/packages/meshbay-hub/tests/test_index_seal_client.py
@@ -69,10 +69,10 @@ def _run(frames: list[str], gek: bytes = GEK) -> dict:
with tempfile.TemporaryDirectory() as tmp:
vectors = Path(tmp) / "vectors.json"
vectors.write_text(json.dumps(
- {"gek": gek.hex(), "group_id": GROUP, "frames": frames}))
+ {"gek": gek.hex(), "group_id": GROUP, "frames": frames}), encoding="utf-8")
proc = subprocess.run(
["node", str(PROBE), str(STATIC), str(vectors), transport_argv()],
- capture_output=True, text=True, timeout=120)
+ capture_output=True, text=True, encoding="utf-8", timeout=120)
if proc.returncode != 0:
pytest.fail(f"probe failed:\n{proc.stderr}")
return json.loads(proc.stdout)
diff --git a/packages/meshbay-hub/tests/test_indexing_dock.py b/packages/meshbay-hub/tests/test_indexing_dock.py
index d69564d..93803a0 100644
--- a/packages/meshbay-hub/tests/test_indexing_dock.py
+++ b/packages/meshbay-hub/tests/test_indexing_dock.py
@@ -33,11 +33,11 @@ needs_node = pytest.mark.skipif(shutil.which("node") is None, reason="node is no
def _run(tmp_path, body: str):
- (tmp_path / "package.json").write_text('{"type":"module"}')
- (tmp_path / "model.js").write_text(MODEL.read_text(encoding="utf-8"))
+ (tmp_path / "package.json").write_text('{"type":"module"}', encoding="utf-8")
+ (tmp_path / "model.js").write_text(MODEL.read_text(encoding="utf-8"), encoding="utf-8")
script = tmp_path / "case.js"
- script.write_text("import * as m from './model.js';\n" + body)
- proc = subprocess.run(["node", str(script)], capture_output=True, text=True,
+ script.write_text("import * as m from './model.js';\n" + body, encoding="utf-8")
+ proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8",
cwd=str(tmp_path))
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
diff --git a/packages/meshbay-hub/tests/test_invite_link_client.py b/packages/meshbay-hub/tests/test_invite_link_client.py
index 313e224..aa8c194 100644
--- a/packages/meshbay-hub/tests/test_invite_link_client.py
+++ b/packages/meshbay-hub/tests/test_invite_link_client.py
@@ -54,8 +54,8 @@ def _module_body() -> str:
def _run(tmp_path, script: str):
harness = tmp_path / "h.js"
- harness.write_text(script)
- out = subprocess.run(["node", str(harness)], capture_output=True, text=True, timeout=60)
+ harness.write_text(script, encoding="utf-8")
+ out = subprocess.run(["node", str(harness)], capture_output=True, encoding="utf-8", timeout=60)
assert out.returncode == 0, out.stderr
return json.loads(out.stdout)
diff --git a/packages/meshbay-hub/tests/test_layout_measured.py b/packages/meshbay-hub/tests/test_layout_measured.py
index 9bf1bde..c5d6280 100644
--- a/packages/meshbay-hub/tests/test_layout_measured.py
+++ b/packages/meshbay-hub/tests/test_layout_measured.py
@@ -78,7 +78,7 @@ def measured(tmp_path_factory):
"""One browser for every width, because launching one apiece cost the
suite three minutes."""
fragment = tmp_path_factory.mktemp("layout") / "fragment.html"
- fragment.write_text(NAV)
+ fragment.write_text(NAV, encoding="utf-8")
proc = subprocess.run(
["python3", str(HARNESS), ",".join(str(w) for w in WIDTHS),
str(fragment), *SELECTORS],
@@ -211,7 +211,7 @@ GROUPED_SELECTORS = [".transfer-panel", ".transfer-head", ".transfer-head-summar
@pytest.fixture(scope="module")
def grouped(tmp_path_factory):
fragment = tmp_path_factory.mktemp("grouped") / "fragment.html"
- fragment.write_text(GROUPED)
+ fragment.write_text(GROUPED, encoding="utf-8")
proc = subprocess.run(
["python3", str(HARNESS), ",".join(str(w) for w in WIDTHS),
str(fragment), *GROUPED_SELECTORS],
diff --git a/packages/meshbay-hub/tests/test_layout_responsive.py b/packages/meshbay-hub/tests/test_layout_responsive.py
index 4ee07d0..bb73557 100644
--- a/packages/meshbay-hub/tests/test_layout_responsive.py
+++ b/packages/meshbay-hub/tests/test_layout_responsive.py
@@ -38,7 +38,7 @@ pytestmark = pytest.mark.skipif(
@pytest.fixture(scope="module")
def css():
- return CSS.read_text()
+ return CSS.read_text(encoding="utf-8")
def _rule(css: str, selector: str) -> str:
@@ -119,7 +119,7 @@ APP = STATIC / "chat-app.js"
@pytest.fixture(scope="module")
def app():
- return APP.read_text()
+ return APP.read_text(encoding="utf-8")
def test_the_chat_panel_is_measured_not_guessed(app):
diff --git a/packages/meshbay-hub/tests/test_locales.py b/packages/meshbay-hub/tests/test_locales.py
index 602d161..05e1115 100644
--- a/packages/meshbay-hub/tests/test_locales.py
+++ b/packages/meshbay-hub/tests/test_locales.py
@@ -35,19 +35,20 @@ EXPECTED = ["en", "fr", "es", "pt-BR", "zh-CN", "ja", "de", "it", "nl", "pl"]
def _sandbox(tmp_path):
"""A directory node will treat as ESM, holding the real sources."""
- (tmp_path / "package.json").write_text('{"type":"module"}')
+ (tmp_path / "package.json").write_text('{"type":"module"}', encoding="utf-8")
(tmp_path / "locales").mkdir(exist_ok=True)
for src in LOCALES.glob("*.js"):
- (tmp_path / "locales" / src.name).write_text(src.read_text())
- (tmp_path / "i18n.js").write_text(I18N.read_text())
+ (tmp_path / "locales" / src.name).write_text(src.read_text(encoding="utf-8"),
+ encoding="utf-8")
+ (tmp_path / "i18n.js").write_text(I18N.read_text(encoding="utf-8"), encoding="utf-8")
return tmp_path
def _node(tmp_path, body):
script = tmp_path / "case.js"
- script.write_text(body)
+ script.write_text(body, encoding="utf-8")
proc = subprocess.run(
- ["node", str(script)], capture_output=True, text=True, cwd=str(tmp_path))
+ ["node", str(script)], capture_output=True, text=True, encoding="utf-8", cwd=str(tmp_path))
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
diff --git a/packages/meshbay-hub/tests/test_media_pager.py b/packages/meshbay-hub/tests/test_media_pager.py
index a8a0569..835ed6f 100644
--- a/packages/meshbay-hub/tests/test_media_pager.py
+++ b/packages/meshbay-hub/tests/test_media_pager.py
@@ -36,8 +36,8 @@ def source():
def _run(tmp_path, source, expr):
script = tmp_path / "case.js"
- script.write_text(f"{source}\nconsole.log(JSON.stringify({expr}));")
- out = subprocess.run(["node", str(script)], capture_output=True, text=True, check=True)
+ script.write_text(f"{source}\nconsole.log(JSON.stringify({expr}));", encoding="utf-8")
+ out = subprocess.run(["node", str(script)], capture_output=True, encoding="utf-8", check=True)
return json.loads(out.stdout)
diff --git a/packages/meshbay-hub/tests/test_member_removal.py b/packages/meshbay-hub/tests/test_member_removal.py
index 7af73a0..5073873 100644
--- a/packages/meshbay-hub/tests/test_member_removal.py
+++ b/packages/meshbay-hub/tests/test_member_removal.py
@@ -27,7 +27,7 @@ pytestmark = pytest.mark.skipif(
def _remove_member_body() -> str:
- source = SETTINGS.read_text()
+ source = SETTINGS.read_text(encoding="utf-8")
start = source.find("const removeMember = useCallback(")
assert start != -1, "removeMember is gone from group-settings.js"
end = source.find("\n }, [", start)
diff --git a/packages/meshbay-hub/tests/test_memory_ceiling.py b/packages/meshbay-hub/tests/test_memory_ceiling.py
index 5984292..9ea6ad3 100644
--- a/packages/meshbay-hub/tests/test_memory_ceiling.py
+++ b/packages/meshbay-hub/tests/test_memory_ceiling.py
@@ -51,7 +51,7 @@ def _lift(name, source):
@pytest.fixture(scope="module")
def target_fn():
"""The ceiling, its error and the real function — read, never re-typed."""
- src = FILE_UTILS.read_text()
+ src = FILE_UTILS.read_text(encoding="utf-8")
ceiling = re.search(r"^const MEMORY_CEILING = .*?;$", src, re.M)
assert ceiling, "MEMORY_CEILING is gone from file-utils.js"
# The test's own CEILING constant must agree with the source's, or every
@@ -123,8 +123,8 @@ try {{
outcome = {{ kind: 'refused', name: err.name, message: err.message }};
}}
console.log(JSON.stringify(outcome));
-""")
- proc = subprocess.run(["node", str(script)], capture_output=True, text=True)
+""", encoding="utf-8")
+ proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8")
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
@@ -259,7 +259,7 @@ def test_no_unguarded_memory_floor(target_fn):
def test_the_guard_is_what_the_preview_uses_too(target_fn):
"""`FilePreview` decrypts a whole entry with no writable at all, so it needs
the same ceiling — and must import it rather than keep a second number."""
- files_app = (STATIC / "files-app.js").read_text()
+ files_app = (STATIC / "files-app.js").read_text(encoding="utf-8")
assert "MEMORY_CEILING" in files_app
assert re.search(r"entry\.size\s*>\s*MEMORY_CEILING", files_app), (
"the preview modal must refuse an oversized entry before fetching it")
diff --git a/packages/meshbay-hub/tests/test_music_queue.py b/packages/meshbay-hub/tests/test_music_queue.py
index 5bf9e97..8f3f9bd 100644
--- a/packages/meshbay-hub/tests/test_music_queue.py
+++ b/packages/meshbay-hub/tests/test_music_queue.py
@@ -123,7 +123,7 @@ def test_an_unreachable_group_is_skipped_whole_rather_than_one_track_at_a_time(s
def test_the_music_wrapper_names_the_op_it_forwards(name):
"""A wrapper that takes two arguments forwards two, and the third is lost
without a word. Both of these did exactly that."""
- src = (STATIC / name).read_text()
+ src = (STATIC / name).read_text(encoding="utf-8")
marker = ("const onPlayQueue = useCallback((tracks, startIndex, op)"
if name == "group-page.js"
else "const handleMusicPlay = useCallback((tracks, startIndex, op)")
diff --git a/packages/meshbay-hub/tests/test_node_page_width_measured.py b/packages/meshbay-hub/tests/test_node_page_width_measured.py
index bd6a231..ae49641 100644
--- a/packages/meshbay-hub/tests/test_node_page_width_measured.py
+++ b/packages/meshbay-hub/tests/test_node_page_width_measured.py
@@ -122,7 +122,7 @@ SELECTORS = ["#node.node-page", "#settings", "#node .node-table-scroll",
def measured(tmp_path_factory):
"""One browser for every width — launching one apiece cost three minutes."""
fragment = tmp_path_factory.mktemp("nodewidth") / "fragment.html"
- fragment.write_text(FRAGMENT)
+ fragment.write_text(FRAGMENT, encoding="utf-8")
proc = subprocess.run(
["python3", str(HARNESS), ",".join(str(w) for w in WIDTHS),
str(fragment), *SELECTORS],
diff --git a/packages/meshbay-hub/tests/test_notification_dismissal.py b/packages/meshbay-hub/tests/test_notification_dismissal.py
index d498b4d..0198dee 100644
--- a/packages/meshbay-hub/tests/test_notification_dismissal.py
+++ b/packages/meshbay-hub/tests/test_notification_dismissal.py
@@ -149,7 +149,7 @@ def test_the_spa_asks_for_unread_only():
exercising it, and here it is the only thing that catches the defect that
actually happened.
"""
- src = APP.read_text()
+ src = APP.read_text(encoding="utf-8")
fetches = re.findall(r"hubFetch\('(/v1/notifications\?[^']*)'", src)
assert fetches, "the notification list fetch is no longer where this reads it"
for url in fetches:
@@ -167,7 +167,7 @@ def test_the_feed_does_not_style_a_state_it_can_no_longer_show():
was dead code that described behaviour the application had abandoned —
and reading it is what made the two halves' disagreement visible.
"""
- src = APP.read_text()
+ src = APP.read_text(encoding="utf-8")
assert "n.read ?" not in src, (
"the feed branches on `read` again; either it is dead code or the "
"dismissal contract has changed and this file should say how")
diff --git a/packages/meshbay-hub/tests/test_offer_retry.py b/packages/meshbay-hub/tests/test_offer_retry.py
index 6389f8e..1239ae0 100644
--- a/packages/meshbay-hub/tests/test_offer_retry.py
+++ b/packages/meshbay-hub/tests/test_offer_retry.py
@@ -33,7 +33,7 @@ pytestmark = pytest.mark.skipif(
def _post(**cfg) -> dict:
proc = subprocess.run(
["node", str(HARNESS), transport_argv(), json.dumps(cfg)],
- capture_output=True, text=True)
+ capture_output=True, text=True, encoding="utf-8")
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
diff --git a/packages/meshbay-hub/tests/test_playlist_crypto.py b/packages/meshbay-hub/tests/test_playlist_crypto.py
index 4ff3080..4dcfcb6 100644
--- a/packages/meshbay-hub/tests/test_playlist_crypto.py
+++ b/packages/meshbay-hub/tests/test_playlist_crypto.py
@@ -69,11 +69,11 @@ const bigBody = (n) => ({
def _run(tmp_path, body):
- src = SRC.read_text().replace("export {", "const _unused_export = {")
+ src = SRC.read_text(encoding="utf-8").replace("export {", "const _unused_export = {")
script = tmp_path / "case.mjs"
- script.write_text(f"{src}\n{PRELUDE}\n{body}\n")
+ script.write_text(f"{src}\n{PRELUDE}\n{body}\n", encoding="utf-8")
out = subprocess.run(["node", str(script)],
- capture_output=True, text=True, timeout=60)
+ capture_output=True, text=True, encoding="utf-8", timeout=60)
assert out.returncode == 0, out.stderr
return json.loads(out.stdout)
diff --git a/packages/meshbay-hub/tests/test_playlist_key.py b/packages/meshbay-hub/tests/test_playlist_key.py
index dbed8ae..261cc32 100644
--- a/packages/meshbay-hub/tests/test_playlist_key.py
+++ b/packages/meshbay-hub/tests/test_playlist_key.py
@@ -58,11 +58,11 @@ globalThis.argon2 = {
def _run(tmp_path, body):
- src = KEYDERIVE.read_text()
+ src = KEYDERIVE.read_text(encoding="utf-8")
script = tmp_path / "case.mjs"
- script.write_text(f"{PRELUDE}\n{src}\n{body}\n")
+ script.write_text(f"{PRELUDE}\n{src}\n{body}\n", encoding="utf-8")
out = subprocess.run(["node", str(script)],
- capture_output=True, text=True, timeout=60)
+ capture_output=True, text=True, encoding="utf-8", timeout=60)
assert out.returncode == 0, out.stderr
return json.loads(out.stdout)
diff --git a/packages/meshbay-hub/tests/test_playlist_merge.py b/packages/meshbay-hub/tests/test_playlist_merge.py
index 2c7b612..dd089e8 100644
--- a/packages/meshbay-hub/tests/test_playlist_merge.py
+++ b/packages/meshbay-hub/tests/test_playlist_merge.py
@@ -39,7 +39,7 @@ EXPORT = re.compile(r"^export \{[^}]*\};?\s*$", re.M | re.S)
@pytest.fixture(scope="module")
def module_source():
- text = SRC.read_text()
+ text = SRC.read_text(encoding="utf-8")
assert not IMPORT.search(text), (
"playlist-merge.js has gained an import. It is executed standalone "
"here, and the merge is untested from the moment it cannot be — keep "
@@ -53,9 +53,9 @@ def module_source():
def _run(tmp_path, module_source, body):
script = tmp_path / "case.js"
- script.write_text(f"{module_source}\n{body}\n")
+ script.write_text(f"{module_source}\n{body}\n", encoding="utf-8")
out = subprocess.run(
- ["node", str(script)], capture_output=True, text=True, timeout=30)
+ ["node", str(script)], capture_output=True, text=True, encoding="utf-8", timeout=30)
assert out.returncode == 0, out.stderr
return json.loads(out.stdout)
diff --git a/packages/meshbay-hub/tests/test_queue_ops.py b/packages/meshbay-hub/tests/test_queue_ops.py
index 64b5ca1..e3853f2 100644
--- a/packages/meshbay-hub/tests/test_queue_ops.py
+++ b/packages/meshbay-hub/tests/test_queue_ops.py
@@ -38,7 +38,7 @@ EXPORT = re.compile(r"^export \{[^}]*\};?\s*$", re.M)
@pytest.fixture(scope="module")
def module_source():
- text = SRC.read_text()
+ text = SRC.read_text(encoding="utf-8")
assert not IMPORT.search(text), (
"queue-ops.js has gained an import. It is executed standalone here, "
"and the queue is untested from the moment it cannot be — keep the "
@@ -52,9 +52,9 @@ def module_source():
def _run(tmp_path, module_source, body):
script = tmp_path / "case.js"
- script.write_text(f"{module_source}\n{body}\n")
+ script.write_text(f"{module_source}\n{body}\n", encoding="utf-8")
out = subprocess.run(
- ["node", str(script)], capture_output=True, text=True, timeout=30)
+ ["node", str(script)], capture_output=True, text=True, encoding="utf-8", timeout=30)
assert out.returncode == 0, out.stderr
return json.loads(out.stdout)
diff --git a/packages/meshbay-hub/tests/test_reconnect_refresh.py b/packages/meshbay-hub/tests/test_reconnect_refresh.py
index ff6d7fb..cd702d3 100644
--- a/packages/meshbay-hub/tests/test_reconnect_refresh.py
+++ b/packages/meshbay-hub/tests/test_reconnect_refresh.py
@@ -43,12 +43,12 @@ def transport():
@pytest.fixture(scope="module")
def group_page():
- return GROUP_PAGE.read_text()
+ return GROUP_PAGE.read_text(encoding="utf-8")
@pytest.fixture(scope="module")
def video_player():
- return VIDEO_PLAYER.read_text()
+ return VIDEO_PLAYER.read_text(encoding="utf-8")
def _reconnect_loop(transport: str) -> str:
@@ -93,7 +93,7 @@ def test_one_listener_throwing_does_not_rob_the_next(transport):
def test_nothing_assigns_the_old_setter():
"""`grep onReconnected =` is what this is, spelled so it cannot rot."""
offenders = [p.name for p in STATIC.glob("*.js")
- if re.search(r"\.onReconnected\s*=", p.read_text())]
+ if re.search(r"\.onReconnected\s*=", p.read_text(encoding="utf-8"))]
assert offenders == [], (
f"{offenders} still assign a slot that no longer exists")
@@ -159,10 +159,10 @@ def test_every_harness_that_renders_the_group_page_stubs_the_subscription():
"""
harness = Path(__file__).parent / "harness"
stubs = [p for p in harness.glob("*.py")
- if "window.MeshBayTransport" in p.read_text()
- and "GroupPage" in p.read_text()]
+ if "window.MeshBayTransport" in p.read_text(encoding="utf-8")
+ and "GroupPage" in p.read_text(encoding="utf-8")]
assert stubs, "no harness stubs the transport any more — has this moved?"
missing = [p.name for p in stubs
- if "addReconnectListener" not in p.read_text()]
+ if "addReconnectListener" not in p.read_text(encoding="utf-8")]
assert missing == [], (
f"{missing} render GroupPage against a node that cannot be subscribed to")
diff --git a/packages/meshbay-hub/tests/test_recovery_key.py b/packages/meshbay-hub/tests/test_recovery_key.py
index 54415f6..e43e6de 100644
--- a/packages/meshbay-hub/tests/test_recovery_key.py
+++ b/packages/meshbay-hub/tests/test_recovery_key.py
@@ -102,10 +102,10 @@ const fp = async (key) => hex(await webcrypto.subtle.encrypt(
def result(tmp_path_factory):
d = tmp_path_factory.mktemp("recovery")
harness = d / "harness.cjs"
- harness.write_text(_HARNESS)
+ harness.write_text(_HARNESS, encoding="utf-8")
proc = subprocess.run(
["node", str(harness), str(KEYDERIVE)],
- capture_output=True, text=True, timeout=120,
+ capture_output=True, text=True, encoding="utf-8", timeout=120,
)
if proc.returncode != 0:
pytest.fail(f"node harness failed:\n{proc.stderr[-2000:]}")
diff --git a/packages/meshbay-hub/tests/test_resume_position.py b/packages/meshbay-hub/tests/test_resume_position.py
index 07ac23a..67eb786 100644
--- a/packages/meshbay-hub/tests/test_resume_position.py
+++ b/packages/meshbay-hub/tests/test_resume_position.py
@@ -85,7 +85,7 @@ def _run(body: str, tmp_path: Path):
f"{body}\n"
"console.log(JSON.stringify(out));\n",
encoding="utf-8")
- proc = subprocess.run(["node", str(script)], capture_output=True, text=True)
+ proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8")
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
diff --git a/packages/meshbay-hub/tests/test_rewrap_fanout.py b/packages/meshbay-hub/tests/test_rewrap_fanout.py
index 7c0f272..b278d0c 100644
--- a/packages/meshbay-hub/tests/test_rewrap_fanout.py
+++ b/packages/meshbay-hub/tests/test_rewrap_fanout.py
@@ -154,10 +154,10 @@ const names = (a) => a.map((x) => x.name).sort();
def result(tmp_path_factory):
d = tmp_path_factory.mktemp("rewrap")
harness = d / "harness.cjs"
- harness.write_text(_HARNESS)
+ harness.write_text(_HARNESS, encoding="utf-8")
proc = subprocess.run(
["node", str(harness), transport_argv()],
- capture_output=True, text=True, timeout=120,
+ capture_output=True, text=True, encoding="utf-8", timeout=120,
)
if proc.returncode != 0:
pytest.fail(f"node harness failed:\n{proc.stderr[-2000:]}")
diff --git a/packages/meshbay-hub/tests/test_search_connect_deadline.py b/packages/meshbay-hub/tests/test_search_connect_deadline.py
index 2d3db06..953f027 100644
--- a/packages/meshbay-hub/tests/test_search_connect_deadline.py
+++ b/packages/meshbay-hub/tests/test_search_connect_deadline.py
@@ -49,7 +49,7 @@ CAP_MS = 30000
def _attempt(**cfg) -> dict:
proc = subprocess.run(
["node", str(HARNESS), search_argv(), json.dumps(cfg)],
- capture_output=True, text=True)
+ capture_output=True, text=True, encoding="utf-8")
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
diff --git a/packages/meshbay-hub/tests/test_search_fanout.py b/packages/meshbay-hub/tests/test_search_fanout.py
index fca879a..23a8b67 100644
--- a/packages/meshbay-hub/tests/test_search_fanout.py
+++ b/packages/meshbay-hub/tests/test_search_fanout.py
@@ -67,7 +67,7 @@ DEAD_MS = 10000 # a node that does not, to the connection deadline
def _sweep(**cfg) -> dict:
proc = subprocess.run(
["node", str(HARNESS), search_argv(), json.dumps(cfg)],
- capture_output=True, text=True)
+ capture_output=True, text=True, encoding="utf-8")
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
diff --git a/packages/meshbay-hub/tests/test_search_files_unmerged.py b/packages/meshbay-hub/tests/test_search_files_unmerged.py
index b84b25d..b6621e5 100644
--- a/packages/meshbay-hub/tests/test_search_files_unmerged.py
+++ b/packages/meshbay-hub/tests/test_search_files_unmerged.py
@@ -38,7 +38,7 @@ MERGE_CALL = "mergeUnitEntries"
def _memo(name):
"""The body of `const <name> = useMemo(() => { ... }, [...]);`."""
- src = SEARCH_PAGE.read_text()
+ src = SEARCH_PAGE.read_text(encoding="utf-8")
m = re.search(
r"^ const " + re.escape(name) + r" = useMemo\(\(\) => \{.*?^ \}, \[.*?\]\);",
src, re.M | re.S)
diff --git a/packages/meshbay-hub/tests/test_search_media_merge.py b/packages/meshbay-hub/tests/test_search_media_merge.py
index 3464902..082de7e 100644
--- a/packages/meshbay-hub/tests/test_search_media_merge.py
+++ b/packages/meshbay-hub/tests/test_search_media_merge.py
@@ -50,7 +50,7 @@ EXPORT = re.compile(r"^export \{[^}]*\};?\s*$", re.M)
def _block(path, header):
"""One top-level `function name(...) {` ... `}` read out of a module."""
- src = path.read_text()
+ src = path.read_text(encoding="utf-8")
m = re.search(r"^" + re.escape(header) + r".*?^\}", src, re.M | re.S)
assert m, (
f"{header} is no longer where this test reads it from in {path.name} — "
@@ -60,7 +60,7 @@ def _block(path, header):
def _const(name):
m = re.search(r"^const " + re.escape(name) + r" = .*?;$",
- SEARCH_PAGE.read_text(), re.M)
+ SEARCH_PAGE.read_text(encoding="utf-8"), re.M)
assert m, f"{name} moved — the search-page units cannot be lifted"
return m.group(0)
@@ -70,7 +70,7 @@ def pipeline():
"""Everything the three views need, in one script."""
return "\n".join([
"const t = (k) => k;",
- EXPORT.sub("", MERGE.read_text()),
+ EXPORT.sub("", MERGE.read_text(encoding="utf-8")),
_block(VIDEO_APP, "function underVideoRoot(entry, directories) {"),
_block(VIDEO_APP, "function buildSeasons(episodes) {"),
_block(VIDEO_APP, "function groupVideoEntries(entries, videoDirectories) {"),
@@ -90,9 +90,9 @@ def pipeline():
def _node(tmp_path, pipeline, body):
script = tmp_path / "case.js"
- script.write_text(f"{pipeline}\n{body}\n")
+ script.write_text(f"{pipeline}\n{body}\n", encoding="utf-8")
out = subprocess.run(
- ["node", str(script)], capture_output=True, text=True, timeout=30)
+ ["node", str(script)], capture_output=True, text=True, encoding="utf-8", timeout=30)
assert out.returncode == 0, out.stderr
return json.loads(out.stdout)
diff --git a/packages/meshbay-hub/tests/test_search_pool.py b/packages/meshbay-hub/tests/test_search_pool.py
index cf9eeb8..89b46cd 100644
--- a/packages/meshbay-hub/tests/test_search_pool.py
+++ b/packages/meshbay-hub/tests/test_search_pool.py
@@ -36,7 +36,7 @@ pytestmark = pytest.mark.skipif(
def _run(**cfg) -> dict:
proc = subprocess.run(
["node", str(HARNESS), search_argv(), json.dumps(cfg)],
- capture_output=True, text=True)
+ capture_output=True, text=True, encoding="utf-8")
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
diff --git a/packages/meshbay-hub/tests/test_search_source_merge.py b/packages/meshbay-hub/tests/test_search_source_merge.py
index b471891..10d10fc 100644
--- a/packages/meshbay-hub/tests/test_search_source_merge.py
+++ b/packages/meshbay-hub/tests/test_search_source_merge.py
@@ -44,7 +44,7 @@ EXPORT = re.compile(r"^export \{[^}]*\};?\s*$", re.M)
@pytest.fixture(scope="module")
def module_source():
- text = SRC.read_text()
+ text = SRC.read_text(encoding="utf-8")
assert not IMPORT.search(text), (
"source-merge.js has gained an import. It is executed standalone here, "
"and the merge is untested from the moment it cannot be — keep the "
@@ -58,9 +58,9 @@ def module_source():
def _run(tmp_path, module_source, body):
script = tmp_path / "case.js"
- script.write_text(f"{module_source}\n{body}\n")
+ script.write_text(f"{module_source}\n{body}\n", encoding="utf-8")
out = subprocess.run(
- ["node", str(script)], capture_output=True, text=True, timeout=30)
+ ["node", str(script)], capture_output=True, text=True, encoding="utf-8", timeout=30)
assert out.returncode == 0, out.stderr
return json.loads(out.stdout)
diff --git a/packages/meshbay-hub/tests/test_season_panel_placement.py b/packages/meshbay-hub/tests/test_season_panel_placement.py
index 8c04ea7..6e5f78b 100644
--- a/packages/meshbay-hub/tests/test_season_panel_placement.py
+++ b/packages/meshbay-hub/tests/test_season_panel_placement.py
@@ -43,7 +43,7 @@ BLOCK = re.compile(
@pytest.fixture(scope="module")
def source():
- m = BLOCK.search(APP.read_text())
+ m = BLOCK.search(APP.read_text(encoding="utf-8"))
assert m, ("placeSeasonPanel is no longer where this test reads it from — "
"the season menu's placement is untested until this is fixed")
return m.group(0)
@@ -58,8 +58,8 @@ def _place(tmp_path, source, *, top, bottom, left=40, width=300, inner_height=74
const el = {{ getBoundingClientRect: () => ({{
top: {top}, bottom: {bottom}, left: {left}, width: {width} }}) }};
console.log(JSON.stringify(placeSeasonPanel(el)));
- """)
- proc = subprocess.run(["node", str(script)], capture_output=True, text=True)
+ """, encoding="utf-8")
+ proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8")
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
@@ -157,7 +157,7 @@ def test_no_element_means_no_position(tmp_path, source):
globalThis.window = {{ innerHeight: 740 }};
{source}
console.log(JSON.stringify(placeSeasonPanel(null)));
- """)
- proc = subprocess.run(["node", str(script)], capture_output=True, text=True)
+ """, encoding="utf-8")
+ proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8")
assert proc.returncode == 0, proc.stderr
assert json.loads(proc.stdout) is None
diff --git a/packages/meshbay-hub/tests/test_session_renewal.py b/packages/meshbay-hub/tests/test_session_renewal.py
index 7b8c108..ecf9ac5 100644
--- a/packages/meshbay-hub/tests/test_session_renewal.py
+++ b/packages/meshbay-hub/tests/test_session_renewal.py
@@ -50,7 +50,7 @@ pytestmark = pytest.mark.skipif(
def _run(scenario: str, app: Path = APP) -> dict:
proc = subprocess.run(
["node", str(HARNESS), str(app), json.dumps({"scenario": scenario})],
- capture_output=True, text=True, timeout=60)
+ capture_output=True, text=True, encoding="utf-8", timeout=60)
assert proc.returncode == 0, f"{proc.stdout}\n{proc.stderr}"
return json.loads(proc.stdout.strip().splitlines()[-1])
@@ -59,14 +59,14 @@ def _run(scenario: str, app: Path = APP) -> dict:
def broken(tmp_path_factory):
"""The client as it shipped: the rotated refresh token dropped."""
out = tmp_path_factory.mktemp("session") / "broken.js"
- src = APP.read_text()
+ src = APP.read_text(encoding="utf-8")
replaced = src.replace(
" refreshToken: data.refresh_token || _auth.refreshToken,",
" refreshToken: _auth.refreshToken,")
assert replaced != src, (
"could not reconstruct the defect — the line it hinged on has moved, "
"and the A/B below would be comparing the fix against itself")
- out.write_text(replaced)
+ out.write_text(replaced, encoding="utf-8")
return out
@@ -186,13 +186,13 @@ def test_the_session_is_much_longer_than_the_token():
def test_renewal_happens_before_expiry_not_after():
"""A margin, so the first click after a long film does not pay for a 401."""
- src = APP.read_text()
+ src = APP.read_text(encoding="utf-8")
import re
margin = int(re.search(r"const TOKEN_RENEW_MARGIN_S = (\d+)", src).group(1))
assert margin >= 300, (
f"{margin} s of margin against a one-hour token is thin: a backgrounded "
"tab has its timers throttled and may not check for minutes")
- assert "visibilitychange" in APP_JS.read_text(), (
+ assert "visibilitychange" in APP_JS.read_text(encoding="utf-8"), (
"nothing re-checks when the tab comes back, which is exactly when the "
"token is most likely to have aged out unnoticed")
@@ -213,7 +213,7 @@ def test_renewing_does_not_tear_down_the_webrtc_connection():
Signing in or out must still re-run it, so the dependency is whether there
is a token, not which one.
"""
- src = GROUP_PAGE.read_text()
+ src = GROUP_PAGE.read_text(encoding="utf-8")
i = src.index("means tearing down the WebRTC connection")
deps = src[i:src.index(");", i)]
assert "Boolean(token)" in deps, (
@@ -228,7 +228,7 @@ def test_the_connection_signs_its_offer_with_a_live_token():
It signs the offer relayed through the hub, where an expired one is a 401
and no connection at all.
"""
- src = GROUP_PAGE.read_text()
+ src = GROUP_PAGE.read_text(encoding="utf-8")
connect = src[src.index("const connect = async () => {"):]
connect = connect[:connect.index("\n };")]
assert "await ensureFreshToken()" in connect, (
diff --git a/packages/meshbay-hub/tests/test_site_basics.py b/packages/meshbay-hub/tests/test_site_basics.py
index a3da39a..78cad83 100644
--- a/packages/meshbay-hub/tests/test_site_basics.py
+++ b/packages/meshbay-hub/tests/test_site_basics.py
@@ -47,7 +47,7 @@ def test_the_icons_are_served_from_the_root(client, path, magic):
def test_meshbay_org_sends_them_to_the_hub(path):
"""The public site owns only the paths its matcher names; a file claimed
there would be looked for in /srv/meshbay/site and 404."""
- m = re.search(r"^\s*@site path (.+)$", CADDYFILE.read_text(), re.M)
+ m = re.search(r"^\s*@site path (.+)$", CADDYFILE.read_text(encoding="utf-8"), re.M)
assert m, "the @site matcher is gone"
assert path not in m.group(1).split()
diff --git a/packages/meshbay-hub/tests/test_spa_ordering.py b/packages/meshbay-hub/tests/test_spa_ordering.py
index 087298f..fdedaf8 100644
--- a/packages/meshbay-hub/tests/test_spa_ordering.py
+++ b/packages/meshbay-hub/tests/test_spa_ordering.py
@@ -132,7 +132,7 @@ COMPONENT_FILES = {
def _component(name: str) -> str:
"""The source of one top-level `function Name(...)`, up to the next one."""
- source = COMPONENT_FILES.get(name, APP).read_text()
+ source = COMPONENT_FILES.get(name, APP).read_text(encoding="utf-8")
start = source.find(f"\nfunction {name}(")
if start == -1:
start = source.find(f"\nexport function {name}(")
@@ -225,7 +225,7 @@ def test_the_uploader_keeps_several_chunks_in_flight():
def test_no_caller_waits_for_one_chunk_at_a_time():
- app = APP.read_text()
+ app = APP.read_text(encoding="utf-8")
assert "uploadChunk(" not in app, (
"a per-chunk await is back in the SPA; use transport.uploadFile()")
@@ -248,7 +248,7 @@ def test_leaving_a_group_hands_the_transport_over_rather_than_closing_it():
def test_signing_out_stops_them():
- app = APP.read_text()
+ app = APP.read_text(encoding="utf-8")
logout = app[app.index(" logout: () => {"):]
logout = logout[:logout.index("navigate('/login')")]
assert "transfers.reset()" in logout, (
@@ -257,7 +257,7 @@ def test_signing_out_stops_them():
def test_the_files_panel_no_longer_carries_its_own_progress_bars():
"""They moved next to the bell, where they stay visible across the app."""
- app = APP.read_text()
+ app = APP.read_text(encoding="utf-8")
for gone in ("setUlState", "setDlState", "dl-bar"):
assert gone not in app, f"{gone} survived the move to the transfer widget"
@@ -270,7 +270,7 @@ def test_a_multi_file_download_waits_for_each_picker():
meant the first opened a dialog and the rest were rejected — two files
selected, one file downloaded.
"""
- app = STATIC.joinpath("files-app.js").read_text()
+ app = STATIC.joinpath("files-app.js").read_text(encoding="utf-8")
# Anchored on the loop rather than on the markup around it: the toolbar
# moved from a dropdown to icon buttons and took the old wrapper with it,
# while the property under test — one picker at a time — did not change.
@@ -289,7 +289,7 @@ def test_links_in_chat_are_built_as_elements_not_markup():
never HTML, and only for http(s) — otherwise javascript: would be one
message away from running here.
"""
- app = STATIC.joinpath("chat-app.js").read_text()
+ app = STATIC.joinpath("chat-app.js").read_text(encoding="utf-8")
fn = app[app.index("function linkify("):]
fn = fn[:fn.index("\nfunction ", 1)]
assert "innerHTML" not in fn and "dangerouslySetInnerHTML" not in fn
diff --git a/packages/meshbay-hub/tests/test_spa_syntax.py b/packages/meshbay-hub/tests/test_spa_syntax.py
index 352f84b..aac4010 100644
--- a/packages/meshbay-hub/tests/test_spa_syntax.py
+++ b/packages/meshbay-hub/tests/test_spa_syntax.py
@@ -47,7 +47,7 @@ def test_every_module_parses(tmp_path):
copy = tmp_path / (path.stem + ".mjs")
copy.write_text(path.read_text(encoding="utf-8"), encoding="utf-8")
proc = subprocess.run(["node", "--check", str(copy)],
- capture_output=True, text=True)
+ capture_output=True, text=True, encoding="utf-8")
if proc.returncode != 0:
first = (proc.stderr or "").strip().splitlines()
detail = next((ln for ln in first if "Error" in ln), first[:1] and first[0] or "")
@@ -67,12 +67,12 @@ def test_the_check_would_notice_a_broken_file(tmp_path):
as_js = tmp_path / "sample.js"
as_js.write_text(bad, encoding="utf-8")
lenient = subprocess.run(["node", "--check", str(as_js)],
- capture_output=True, text=True)
+ capture_output=True, text=True, encoding="utf-8")
as_mjs = tmp_path / "sample.mjs"
as_mjs.write_text(bad, encoding="utf-8")
strict = subprocess.run(["node", "--check", str(as_mjs)],
- capture_output=True, text=True)
+ capture_output=True, text=True, encoding="utf-8")
assert strict.returncode != 0, (
"the .mjs check no longer reports a module syntax error — this whole "
diff --git a/packages/meshbay-hub/tests/test_streamed_download_reliability.py b/packages/meshbay-hub/tests/test_streamed_download_reliability.py
index e1b3800..e60e833 100644
--- a/packages/meshbay-hub/tests/test_streamed_download_reliability.py
+++ b/packages/meshbay-hub/tests/test_streamed_download_reliability.py
@@ -195,8 +195,8 @@ def _run(tmp_path, body, *, control_after_ms=0, active=True,
controlled_at_load=False, registered_at_load=False,
worker_asleep=False):
module = tmp_path / "downloads.mjs"
- module.write_text(DOWNLOADS.read_text())
- (tmp_path / "package.json").write_text('{"type":"module"}')
+ module.write_text(DOWNLOADS.read_text(encoding="utf-8"), encoding="utf-8")
+ (tmp_path / "package.json").write_text('{"type":"module"}', encoding="utf-8")
plan = {
"controlAfterMs": control_after_ms,
"active": active,
@@ -211,12 +211,12 @@ def _run(tmp_path, body, *, control_after_ms=0, active=True,
}
script = tmp_path / "case.mjs"
script.write_text(
- (PRELUDE % {"plan": json.dumps(plan), "module": module.as_posix(),
+ (PRELUDE % {"plan": json.dumps(plan), "module": module.as_uri(),
"control": control_budget_ms,
"controlled": json.dumps(controlled_at_load)})
+ body
- + "\nout.log = log;\nconsole.log(JSON.stringify(out));\n")
- proc = subprocess.run(["node", str(script)], capture_output=True, text=True,
+ + "\nout.log = log;\nconsole.log(JSON.stringify(out));\n", encoding="utf-8")
+ proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8",
timeout=120)
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
@@ -338,7 +338,7 @@ def test_the_worker_is_primed_at_boot_not_at_the_first_click(tmp_path):
from a module that actually imports it — `node --check` would not notice a
missing import, which is a mistake this repo has already shipped once.
"""
- app = (STATIC / "app.js").read_text()
+ app = (STATIC / "app.js").read_text(encoding="utf-8")
assert "downloads.primeServiceWorker()" in app, "nothing primes the worker"
assert "import * as downloads from './downloads.js'" in app, (
"app.js calls downloads.primeServiceWorker() without importing downloads")
@@ -350,7 +350,7 @@ def test_the_worker_is_primed_at_boot_not_at_the_first_click(tmp_path):
def test_the_worker_answers_a_re_claim(tmp_path):
"""The page's last resort before declaring the path unavailable only works
if sw.js implements the other half."""
- sw = (STATIC / "sw.js").read_text()
+ sw = (STATIC / "sw.js").read_text(encoding="utf-8")
assert "mbdl-claim" in sw and "clients.claim()" in sw
diff --git a/packages/meshbay-hub/tests/test_table_rows_measured.py b/packages/meshbay-hub/tests/test_table_rows_measured.py
index f203624..b1f36ea 100644
--- a/packages/meshbay-hub/tests/test_table_rows_measured.py
+++ b/packages/meshbay-hub/tests/test_table_rows_measured.py
@@ -61,7 +61,7 @@ SELECTORS = [f"tbody tr:nth-child({r}) td:nth-child({c})"
@pytest.fixture(scope="module")
def measured(tmp_path_factory):
fragment = tmp_path_factory.mktemp("table") / "fragment.html"
- fragment.write_text(TABLE)
+ fragment.write_text(TABLE, encoding="utf-8")
proc = subprocess.run(
["python3", str(HARNESS), ",".join(str(w) for w in WIDTHS),
str(fragment), *SELECTORS],
diff --git a/packages/meshbay-hub/tests/test_transfers.py b/packages/meshbay-hub/tests/test_transfers.py
index b1bac4a..1d0c0f0 100644
--- a/packages/meshbay-hub/tests/test_transfers.py
+++ b/packages/meshbay-hub/tests/test_transfers.py
@@ -26,15 +26,15 @@ pytestmark = pytest.mark.skipif(
def _run(body, tmp_path):
module = tmp_path / "transfers.mjs"
- module.write_text(TRANSFERS.read_text())
+ module.write_text(TRANSFERS.read_text(encoding="utf-8"), encoding="utf-8")
script = tmp_path / "case.mjs"
script.write_text(
- f"import {{ TransferStore, formatSpeed }} from '{module.as_posix()}';\n"
+ f"import {{ TransferStore, formatSpeed }} from '{module.as_uri()}';\n"
"const out = [];\n"
"const say = (...a) => out.push(...a);\n"
f"{body}\n"
- "console.log(JSON.stringify(out));\n")
- proc = subprocess.run(["node", str(script)], capture_output=True, text=True)
+ "console.log(JSON.stringify(out));\n", encoding="utf-8")
+ proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8")
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
@@ -388,11 +388,11 @@ def test_asking_for_a_slot_on_a_dead_channel_does_not_throw(tmp_path):
# first time it arms its watchdog.
start = src.index("const LEASE_WATCHDOG_MS")
end = src.index("\nclass MeshBayTransport")
- module.write_text(src[start:end] + "\nexport { Lease };\n")
+ module.write_text(src[start:end] + "\nexport { Lease };\n", encoding="utf-8")
script = tmp_path / "case.mjs"
script.write_text(f"""
-import {{ Lease }} from '{module.as_posix()}';
+import {{ Lease }} from '{module.as_uri()}';
const out = [];
const transport = {{
supportsTransferSlots: true,
@@ -409,8 +409,8 @@ try {{ lease.release('cancelled'); out.push('release ok'); }}
catch (e) {{ out.push('release threw: ' + e.message); }}
clearTimeout(lease._watchdog);
console.log(JSON.stringify(out));
-""")
- proc = subprocess.run(["node", str(script)], capture_output=True, text=True)
+""", encoding="utf-8")
+ proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8")
assert proc.returncode == 0, proc.stderr
out = json.loads(proc.stdout)
assert out[0] is None, f"asking for a slot threw: {out[0]}"
@@ -433,7 +433,7 @@ def test_the_slot_is_asked_for_after_there_is_somewhere_to_write():
Source-reading, because the ordering is the whole property and it has no
behaviour of its own to drive: what matters is which call comes first.
"""
- src = (STATIC / "file-utils.js").read_text()
+ src = (STATIC / "file-utils.js").read_text(encoding="utf-8")
fn = src[src.index("async function downloadEntry"):]
fn = fn[:fn.index("\n}\n")]
# `_openTargetInTurn` since target openings were serialised — same call,
@@ -832,7 +832,7 @@ def test_a_paused_transfer_is_not_filed_under_finished(tmp_path):
here: a copy of them in this file would agree with a broken version by
construction.
"""
- src = (STATIC / "app.js").read_text()
+ src = (STATIC / "app.js").read_text(encoding="utf-8")
start = src.index(" const running = items.filter(")
block = src[start:src.index("const active =", start)]
@@ -851,8 +851,8 @@ const items = [
const seen = { running, waiting, paused, finished };
console.log(JSON.stringify(Object.fromEntries(
Object.entries(seen).map(([k, v]) => [k, v.map(i => i.id)]))));
-""")
- proc = subprocess.run(["node", str(script)], capture_output=True, text=True)
+""", encoding="utf-8")
+ proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8")
assert proc.returncode == 0, proc.stderr
groups = json.loads(proc.stdout)
@@ -870,7 +870,7 @@ def test_a_paused_transfer_still_counts_as_active(tmp_path):
"""The badge says how much is going on. A paused transfer is not over — the
person means to come back to it — so counting it as nothing would be a
panel that says "0" over work that is still there."""
- src = (STATIC / "app.js").read_text()
+ src = (STATIC / "app.js").read_text(encoding="utf-8")
start = src.index(" const running = items.filter(")
block = src[start:src.index("\n\n", src.index("const active =", start))]
@@ -879,8 +879,8 @@ def test_a_paused_transfer_still_counts_as_active(tmp_path):
const items = [{ id: 1, status: 'paused' }, { id: 2, status: 'done' }];
""" + block + """
console.log(JSON.stringify({ active }));
-""")
- proc = subprocess.run(["node", str(script)], capture_output=True, text=True)
+""", encoding="utf-8")
+ proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8")
assert proc.returncode == 0, proc.stderr
assert json.loads(proc.stdout)["active"] == 1
@@ -897,7 +897,7 @@ def test_a_row_that_cannot_pause_says_so_where_the_button_would_be():
Shown only where a folder can actually be chosen: Firefox and Safari have
none to choose, and "choose a folder" would be advice that cannot be taken.
"""
- src = (STATIC / "app.js").read_text()
+ src = (STATIC / "app.js").read_text(encoding="utf-8")
row = src[src.index("function TransferRow"):]
row = row[:row.index("\n}\n")]
@@ -917,4 +917,4 @@ def test_the_reason_is_translated_everywhere():
"""`t()` falls back to the key, so a missing catalogue entry shows
`transfers.not_pausable` in a tooltip rather than a sentence."""
for path in sorted((STATIC / "locales").glob("*.js")):
- assert "'transfers.not_pausable'" in path.read_text(), path.name
+ assert "'transfers.not_pausable'" in path.read_text(encoding="utf-8"), path.name
diff --git a/packages/meshbay-hub/tests/test_transport_contracts.py b/packages/meshbay-hub/tests/test_transport_contracts.py
index 978c2e5..c1ca36b 100644
--- a/packages/meshbay-hub/tests/test_transport_contracts.py
+++ b/packages/meshbay-hub/tests/test_transport_contracts.py
@@ -59,22 +59,22 @@ def transport():
@pytest.fixture(scope="module")
def app():
- return APP.read_text()
+ return APP.read_text(encoding="utf-8")
@pytest.fixture(scope="module")
def chat():
- return CHAT_APP.read_text()
+ return CHAT_APP.read_text(encoding="utf-8")
@pytest.fixture(scope="module")
def group_page():
- return GROUP_PAGE.read_text()
+ return GROUP_PAGE.read_text(encoding="utf-8")
@pytest.fixture(scope="module")
def create_group():
- return CREATE_GROUP.read_text()
+ return CREATE_GROUP.read_text(encoding="utf-8")
def test_chat_history_pages_backwards(transport):
@@ -187,7 +187,7 @@ def test_messages_are_keyed_by_id_not_index(chat):
def test_presence_has_three_states_and_a_label_for_each(app):
for state in ("online", "offline", "unknown"):
- assert f"presence-{state}" in (STATIC / "style.css").read_text()
+ assert f"presence-{state}" in (STATIC / "style.css").read_text(encoding="utf-8")
assert "t('presence.' + state)" in app, (
"red and green are the pair colour-blind readers cannot separate, so "
"the dot needs a title and an aria-label, not just a colour")
@@ -278,7 +278,7 @@ def test_no_setter_survives_the_state_it_belonged_to():
"""
import re
for path in SPLIT_FILES:
- app = path.read_text()
+ app = path.read_text(encoding="utf-8")
declared = set(re.findall(r"const \[\s*\w+\s*,\s*(set\w+)\s*\]\s*=\s*useState", app))
# Names brought in from another module are defined, just not here.
imported = set()
diff --git a/packages/meshbay-hub/tests/test_upload_seal_client.py b/packages/meshbay-hub/tests/test_upload_seal_client.py
index bcda14c..f62aa68 100644
--- a/packages/meshbay-hub/tests/test_upload_seal_client.py
+++ b/packages/meshbay-hub/tests/test_upload_seal_client.py
@@ -48,10 +48,10 @@ BODY = bytes(range(256)) * 3 # 768 bytes → 24 chunks of 32
def _run_probe(payload: dict) -> dict:
with tempfile.TemporaryDirectory() as d:
f = Path(d) / "input.json"
- f.write_text(json.dumps(payload))
+ f.write_text(json.dumps(payload), encoding="utf-8")
proc = subprocess.run(
["node", str(PROBE), str(STATIC), str(f), transport_argv()],
- capture_output=True, text=True, timeout=60,
+ capture_output=True, text=True, encoding="utf-8", timeout=60,
)
if proc.returncode != 0 or not proc.stdout:
pytest.fail(f"upload probe failed:\n{proc.stderr}")
diff --git a/packages/meshbay-hub/tests/test_versions_agree.py b/packages/meshbay-hub/tests/test_versions_agree.py
index 4466c93..46887c0 100644
--- a/packages/meshbay-hub/tests/test_versions_agree.py
+++ b/packages/meshbay-hub/tests/test_versions_agree.py
@@ -27,11 +27,11 @@ PACKAGES = ROOT / "packages"
def _python_versions() -> dict[str, str]:
found = {}
for pyproject in sorted(PACKAGES.glob("*/pyproject.toml")):
- m = re.search(r'^version = "([^"]+)"', pyproject.read_text(), re.M)
+ m = re.search(r'^version = "([^"]+)"', pyproject.read_text(encoding="utf-8"), re.M)
if m:
found[f"{pyproject.parent.name}/pyproject.toml"] = m.group(1)
for init in sorted(PACKAGES.glob("*/src/*/__init__.py")):
- m = re.search(r'^__version__ = "([^"]+)"', init.read_text(), re.M)
+ m = re.search(r'^__version__ = "([^"]+)"', init.read_text(encoding="utf-8"), re.M)
if m:
found[f"{init.parent.name}/__init__.py"] = m.group(1)
return found
@@ -41,7 +41,7 @@ def _client_version() -> str | None:
pkg = PACKAGES / "meshbay-client" / "package.json"
if not pkg.exists():
return None
- return json.loads(pkg.read_text()).get("version")
+ return json.loads(pkg.read_text(encoding="utf-8")).get("version")
@pytest.mark.skipif(not PACKAGES.is_dir(), reason="package layout not present")
diff --git a/packages/meshbay-hub/tests/test_video_audio_track.py b/packages/meshbay-hub/tests/test_video_audio_track.py
index 54beca2..82d6e18 100644
--- a/packages/meshbay-hub/tests/test_video_audio_track.py
+++ b/packages/meshbay-hub/tests/test_video_audio_track.py
@@ -42,7 +42,7 @@ pytestmark = pytest.mark.skipif(
@pytest.fixture(scope="module")
def app():
- return APP.read_text()
+ return APP.read_text(encoding="utf-8")
def _player(app: str) -> str:
@@ -88,10 +88,10 @@ def _label_cases(tmp_path, app, cases, locale="en"):
"const t = (k, p) => `${k}:${p.n}`;\n"
+ src
+ "\nconst out = JSON.parse(process.argv[2]).map(audioTrackLabel);\n"
- "console.log(JSON.stringify(out));\n")
+ "console.log(JSON.stringify(out));\n", encoding="utf-8")
proc = subprocess.run(
["node", str(script), json.dumps(cases)],
- capture_output=True, text=True)
+ capture_output=True, text=True, encoding="utf-8")
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
diff --git a/packages/meshbay-hub/tests/test_video_buffer_ceiling.py b/packages/meshbay-hub/tests/test_video_buffer_ceiling.py
index a488976..da9766e 100644
--- a/packages/meshbay-hub/tests/test_video_buffer_ceiling.py
+++ b/packages/meshbay-hub/tests/test_video_buffer_ceiling.py
@@ -67,7 +67,7 @@ pytestmark = pytest.mark.skipif(
@pytest.fixture(scope="module")
def app():
- return APP.read_text()
+ return APP.read_text(encoding="utf-8")
def _player(app: str) -> str:
@@ -95,7 +95,7 @@ HARNESS = Path(__file__).parent / "harness" / "mse_harness.mjs"
def _harness(**cfg) -> dict:
proc = subprocess.run(
["node", str(HARNESS), str(APP), json.dumps(cfg)],
- capture_output=True, text=True)
+ capture_output=True, text=True, encoding="utf-8")
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
@@ -437,7 +437,7 @@ def test_credit_is_a_window_and_not_a_debt(app):
pump = pump[:pump.index("\n }, [")]
assert "STREAM_WINDOW - outstandingRef.current" in pump, (
"pump() no longer tops a window up to what is allowed in flight")
- src = APP.read_text()
+ src = APP.read_text(encoding="utf-8")
window = int(re.search(r"const STREAM_WINDOW = (\d+)", src).group(1))
assert 2 <= window <= 16, (
f"a window of {window} segments is either too small to keep the pipe "
diff --git a/packages/meshbay-hub/tests/test_video_default_season.py b/packages/meshbay-hub/tests/test_video_default_season.py
index 898d3f5..ab1d7c1 100644
--- a/packages/meshbay-hub/tests/test_video_default_season.py
+++ b/packages/meshbay-hub/tests/test_video_default_season.py
@@ -43,7 +43,7 @@ BLOCK = re.compile(r"^function defaultSeason\(show\) \{.*?^\}", re.M | re.S)
@pytest.fixture(scope="module")
def source():
- m = BLOCK.search(APP.read_text())
+ m = BLOCK.search(APP.read_text(encoding="utf-8"))
assert m, ("defaultSeason is no longer where this test reads it from — the "
"season a show opens on is untested until this is fixed")
return m.group(0)
@@ -57,8 +57,8 @@ def _default(tmp_path, source, seasons):
script.write_text(f"""
{source}
console.log(JSON.stringify(defaultSeason({json.dumps(show)})));
- """)
- proc = subprocess.run(["node", str(script)], capture_output=True, text=True)
+ """, encoding="utf-8")
+ proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8")
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
diff --git a/packages/meshbay-hub/tests/test_video_detail_measured.py b/packages/meshbay-hub/tests/test_video_detail_measured.py
index 1ac8586..4cd5969 100644
--- a/packages/meshbay-hub/tests/test_video_detail_measured.py
+++ b/packages/meshbay-hub/tests/test_video_detail_measured.py
@@ -182,7 +182,7 @@ SELECTORS = [
def measured(tmp_path_factory):
"""One browser for every width — launching one apiece cost three minutes."""
fragment = tmp_path_factory.mktemp("videodetail") / "fragment.html"
- fragment.write_text(FRAGMENT)
+ fragment.write_text(FRAGMENT, encoding="utf-8")
proc = subprocess.run(
["python3", str(HARNESS), ",".join(str(w) for w in WIDTHS),
str(fragment), *SELECTORS],
@@ -292,7 +292,7 @@ def test_the_episode_list_reserves_its_scrollbar():
rectangle — `test_layout_responsive.py` says so at length — but it is
worth more than a test that cannot fail.
"""
- css = (STATIC / "style.css").read_text()
+ css = (STATIC / "style.css").read_text(encoding="utf-8")
rule = re.search(
r"\.video-detail\.video-detail-steady \.video-season-list \{([^}]*)\}", css)
assert rule, "the steady episode-list rule is gone"
diff --git a/packages/meshbay-hub/tests/test_video_reconnect.py b/packages/meshbay-hub/tests/test_video_reconnect.py
index beeeace..91aa87b 100644
--- a/packages/meshbay-hub/tests/test_video_reconnect.py
+++ b/packages/meshbay-hub/tests/test_video_reconnect.py
@@ -53,7 +53,7 @@ pytestmark = pytest.mark.skipif(
@pytest.fixture(scope="module")
def app():
- return APP.read_text()
+ return APP.read_text(encoding="utf-8")
def _player(app: str) -> str:
@@ -87,7 +87,7 @@ def _plan(app: str, cases: list[dict]) -> list[dict]:
"(c) => reconnectPlan(c.playhead, c.range, c.ended, c.cast))));"
)
proc = subprocess.run(["node", "--input-type=module", "-e", script],
- capture_output=True, text=True)
+ capture_output=True, text=True, encoding="utf-8")
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
diff --git a/packages/meshbay-hub/tests/test_video_seek.py b/packages/meshbay-hub/tests/test_video_seek.py
index 9436065..3289eda 100644
--- a/packages/meshbay-hub/tests/test_video_seek.py
+++ b/packages/meshbay-hub/tests/test_video_seek.py
@@ -46,7 +46,7 @@ pytestmark = pytest.mark.skipif(not APP.exists(), reason="SPA sources unavailabl
@pytest.fixture(scope="module")
def app():
- return APP.read_text()
+ return APP.read_text(encoding="utf-8")
def _player(app: str) -> str:
@@ -164,7 +164,7 @@ def test_the_leak_deadlocks_the_window_and_the_fix_clears_it():
proc = subprocess.run(
["node", str(harness), str(APP),
json.dumps({"decrementFirst": decrement_first})],
- capture_output=True, text=True)
+ capture_output=True, text=True, encoding="utf-8")
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
@@ -269,7 +269,7 @@ def test_seeks_are_debounced(app):
"""Dragging fires `seeking` continuously; each one we act on costs a spawn."""
player = _player(app)
assert "SEEK_DEBOUNCE_MS" in player, "every intermediate drag position seeks"
- ms = int(re.search(r"const SEEK_DEBOUNCE_MS = (\d+)", APP.read_text()).group(1))
+ ms = int(re.search(r"const SEEK_DEBOUNCE_MS = (\d+)", APP.read_text(encoding="utf-8")).group(1))
assert 150 <= ms <= 1000, (
f"{ms} ms is either short enough to still storm the node or long "
"enough to feel broken")
@@ -289,7 +289,7 @@ def test_a_seek_inside_the_buffer_does_not_reach_the_node(app):
def test_the_position_is_kept_in_this_browser(app):
"""localStorage: no protocol, no storage for anyone else to keep, and
nothing new learns what you watch."""
- src = APP.read_text()
+ src = APP.read_text(encoding="utf-8")
assert "mb:pos:" in src, "no position is stored"
read = src[src.index("function readResumePosition"):]
read = read[:read.index("\n}")]
@@ -299,7 +299,7 @@ def test_the_position_is_kept_in_this_browser(app):
def test_a_finished_film_does_not_offer_to_resume(app):
- src = APP.read_text()
+ src = APP.read_text(encoding="utf-8")
write = src[src.index("function writeResumePosition"):]
write = write[:write.index("\n}")]
assert "RESUME_MAX_FRACTION" in write and "removeItem" in write, (
@@ -317,6 +317,6 @@ def test_the_viewer_can_refuse_the_resume(app):
@pytest.mark.parametrize("locale", ["en", "fr", "es", "pt-BR", "zh-CN", "ja",
"de", "it", "nl", "pl"])
def test_the_resume_strings_exist_everywhere(locale):
- text = (STATIC / "locales" / f"{locale}.js").read_text()
+ text = (STATIC / "locales" / f"{locale}.js").read_text(encoding="utf-8")
for key in ("video.resumed_at", "video.from_start"):
assert key in text, f"{locale} is missing {key}"
diff --git a/packages/meshbay-hub/tests/test_video_stream_switch.py b/packages/meshbay-hub/tests/test_video_stream_switch.py
index 7859057..6c67430 100644
--- a/packages/meshbay-hub/tests/test_video_stream_switch.py
+++ b/packages/meshbay-hub/tests/test_video_stream_switch.py
@@ -45,7 +45,7 @@ pytestmark = pytest.mark.skipif(
@pytest.fixture(scope="module")
def app():
- return APP.read_text()
+ return APP.read_text(encoding="utf-8")
def _player(app: str) -> str:
@@ -125,8 +125,8 @@ def test_the_stall_is_reproduced_and_the_reset_clears_it(tmp_path):
out[name] = p.st.appended - afterFirst;
}
console.log(JSON.stringify(out));
- """)
- proc = subprocess.run(["node", str(script)], capture_output=True, text=True)
+ """, encoding="utf-8")
+ proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8")
assert proc.returncode == 0, proc.stderr
got = json.loads(proc.stdout)
diff --git a/packages/meshbay-hub/tests/test_video_subtitles.py b/packages/meshbay-hub/tests/test_video_subtitles.py
index 5a4b215..68058cd 100644
--- a/packages/meshbay-hub/tests/test_video_subtitles.py
+++ b/packages/meshbay-hub/tests/test_video_subtitles.py
@@ -47,7 +47,7 @@ pytestmark = pytest.mark.skipif(
@pytest.fixture(scope="module")
def app():
- return APP.read_text()
+ return APP.read_text(encoding="utf-8")
@pytest.fixture(scope="module")
@@ -117,10 +117,10 @@ def _label_cases(tmp_path, app, cases, locale="en"):
"const t = (k, p) => (p ? `${k}:${p.n}` : k);\n"
+ src
+ "\nconst out = JSON.parse(process.argv[2]).map(subtitleTrackLabel);\n"
- "console.log(JSON.stringify(out));\n")
+ "console.log(JSON.stringify(out));\n", encoding="utf-8")
proc = subprocess.run(
["node", str(script), json.dumps(cases)],
- capture_output=True, text=True)
+ capture_output=True, text=True, encoding="utf-8")
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
diff --git a/packages/meshbay-hub/tests/test_zip_size_limit.py b/packages/meshbay-hub/tests/test_zip_size_limit.py
index 9243fd1..6ef0989 100644
--- a/packages/meshbay-hub/tests/test_zip_size_limit.py
+++ b/packages/meshbay-hub/tests/test_zip_size_limit.py
@@ -46,7 +46,7 @@ def _run(total_bytes, tmp_path, picker=False):
for src in STATIC.glob("*.js"):
(sandbox / src.name).write_text(src.read_text(encoding="utf-8"),
encoding="utf-8")
- (tmp_path / "package.json").write_text('{"type":"module"}')
+ (tmp_path / "package.json").write_text('{"type":"module"}', encoding="utf-8")
# ask.js draws a dialog in the DOM, which Node has none of; the question is
# answered here instead, exactly where `confirm` used to be stubbed.
(sandbox / "ask.js").write_text(
@@ -87,7 +87,7 @@ if ({picker_js}) {{
}});
}}
-const M = await import('{(sandbox / "file-utils.js").as_posix()}');
+const M = await import('{(sandbox / "file-utils.js").as_uri()}');
// Faithful enough to the real store: it runs `prepare` and honours what it
// returns. The target is opened there now — the row exists from the click and
@@ -126,7 +126,7 @@ out.limit = M.ZIP_MAX_BYTES;
console.log(JSON.stringify(out));
""", encoding="utf-8")
- proc = subprocess.run(["node", str(script)], capture_output=True, text=True)
+ proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8")
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
diff --git a/packages/meshbay-hub/tests/test_zipstream.py b/packages/meshbay-hub/tests/test_zipstream.py
index 51a42d0..55a4d97 100644
--- a/packages/meshbay-hub/tests/test_zipstream.py
+++ b/packages/meshbay-hub/tests/test_zipstream.py
@@ -32,12 +32,12 @@ def _build(files, force_zip64=False, tmp_path=None):
# <script type="module">, but Node reads a bare .js as CommonJS unless a
# package.json says otherwise, and there is none next to the SPA.
module = tmp_path / "zipstream.mjs"
- module.write_text(ZIPSTREAM.read_text())
+ module.write_text(ZIPSTREAM.read_text(encoding="utf-8"), encoding="utf-8")
script = tmp_path / "build.mjs"
out = tmp_path / "out.zip"
script.write_text(f"""
import {{ writeFileSync }} from 'node:fs';
-import {{ ZipStream }} from '{module.as_posix()}';
+import {{ ZipStream }} from '{module.as_uri()}';
const files = {json.dumps({k: list(v) for k, v in files.items()})};
const parts = [];
@@ -55,8 +55,8 @@ for (const [name, bytes] of Object.entries(files)) {{
}}
await zip.finish();
writeFileSync('{out.as_posix()}', Buffer.concat(parts));
-""")
- proc = subprocess.run(["node", str(script)], capture_output=True, text=True)
+""", encoding="utf-8")
+ proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8")
assert proc.returncode == 0, proc.stderr
return out.read_bytes()
@@ -140,14 +140,14 @@ def test_an_empty_archive_is_still_an_archive(tmp_path):
def _under(entries, dir_, tmp_path):
module = tmp_path / "zipstream.mjs"
- module.write_text(ZIPSTREAM.read_text())
+ module.write_text(ZIPSTREAM.read_text(encoding="utf-8"), encoding="utf-8")
script = tmp_path / "under.mjs"
script.write_text(f"""
-import {{ entriesUnder }} from '{module.as_posix()}';
+import {{ entriesUnder }} from '{module.as_uri()}';
const out = entriesUnder({json.dumps(entries)}, {json.dumps(dir_)});
console.log(JSON.stringify(out.map(o => o.name)));
-""")
- proc = subprocess.run(["node", str(script)], capture_output=True, text=True)
+""", encoding="utf-8")
+ proc = subprocess.run(["node", str(script)], capture_output=True, text=True, encoding="utf-8")
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
diff --git a/packages/meshbay-node/tests/golden/cli.json b/packages/meshbay-node/tests/golden/cli.json
index 8db5abd..71fa336 100644
--- a/packages/meshbay-node/tests/golden/cli.json
+++ b/packages/meshbay-node/tests/golden/cli.json
@@ -102,7 +102,7 @@
"asked": [],
"exit": 0,
"stderr": "",
- "stdout": "encryption always on (MNP 3.4)\nepoch 1\nmessages 0 encrypted, 0 in the clear\n",
+ "stdout": "encryption always on (MNP <version>)\nepoch 1\nmessages 0 encrypted, 0 in the clear\n",
"systemctl": []
},
"denylist clear": {
@@ -423,7 +423,7 @@
"api": [],
"asked": [],
"exit": 2,
- "stderr": "usage: meshbay-node [-h] [--hub-url HUB_URL] [--username USERNAME] [--dir DIR] [--yes]\n [--config CONFIG] [--group GROUP] [--link] [--writable] [--no-writable]\n [--removable] [--no-removable] [--name NAME]\n [--log-level {DEBUG,INFO,WARNING,ERROR}]\n [{init,reset,status,gek-init,gek,operator,member,group,root,file,video,chat,denylist,stun,transfers,reload,restart-daemon,autostart,service,calibrate-argon2}]\n [subcommand] [target] [value]\nmeshbay-node: error: argument command: invalid choice: 'no-such-verb' (choose from 'init', 'reset', 'status', 'gek-init', 'gek', 'operator', 'member', 'group', 'root', 'file', 'video', 'chat', 'denylist', 'stun', 'transfers', 'reload', 'restart-daemon', 'autostart', 'service', 'calibrate-argon2')\n",
+ "stderr": "usage: meshbay-node [-h] [--hub-url HUB_URL] [--username USERNAME] [--dir DIR] [--yes]\n [--config CONFIG] [--group GROUP] [--link] [--writable] [--no-writable]\n [--removable] [--no-removable] [--name NAME]\n [--log-level {DEBUG,INFO,WARNING,ERROR}]\n [{init,reset,status,gek-init,gek,operator,member,group,root,file,video,chat,denylist,stun,transfers,reload,restart-daemon,autostart,service,calibrate-argon2}]\n [subcommand] [target] [value]\nmeshbay-node: error: argument command: invalid choice: 'no-such-verb' (choose from init, reset, status, gek-init, gek, operator, member, group, root, file, video, chat, denylist, stun, transfers, reload, restart-daemon, autostart, service, calibrate-argon2)\n",
"stdout": "",
"systemctl": []
},
diff --git a/packages/meshbay-node/tests/test_added_root_does_not_wait_for_its_scan.py b/packages/meshbay-node/tests/test_added_root_does_not_wait_for_its_scan.py
index 4d4c4e8..0a9762d 100644
--- a/packages/meshbay-node/tests/test_added_root_does_not_wait_for_its_scan.py
+++ b/packages/meshbay-node/tests/test_added_root_does_not_wait_for_its_scan.py
@@ -110,7 +110,7 @@ async def test_the_daemon_serves_the_new_set_and_releases_its_lock(tmp_path):
f'data_dir = "{(tmp_path / "data").as_posix()}"\n\n'
f'[[groups]]\nid = "{GROUP}"\nname = "plop"\n\n'
f' [[groups.roots]]\n path = "{one.as_posix()}"\n name = "one"\n\n'
- f' [[groups.roots]]\n path = "{two.as_posix()}"\n name = "two"\n')
+ f' [[groups.roots]]\n path = "{two.as_posix()}"\n name = "two"\n', encoding="utf-8")
idx = await _indexer(one)
ctx = {"roots": idx.roots}
diff --git a/packages/meshbay-node/tests/test_audio_meta_cache.py b/packages/meshbay-node/tests/test_audio_meta_cache.py
index e6ee315..835d3f6 100644
--- a/packages/meshbay-node/tests/test_audio_meta_cache.py
+++ b/packages/meshbay-node/tests/test_audio_meta_cache.py
@@ -14,6 +14,7 @@ old name's answer, which is the fault `_reenrich_renamed_audio_entries` exists
to prevent.
"""
import asyncio
+import shutil
import subprocess
from pathlib import Path
@@ -23,8 +24,9 @@ from meshbay_node.indexer import enrich_audio
from meshbay_node.indexer.enrich_audio import AudioEnricher
from meshbay_node.media_cache import MediaCache
-_HAVE_FFMPEG = (
- subprocess.run(["which", "ffmpeg"], capture_output=True).returncode == 0)
+# shutil.which, not `which`: there is no `which` outside Git Bash on Windows,
+# and the suite stopped at collection when run from PowerShell.
+_HAVE_FFMPEG = shutil.which("ffmpeg") is not None
pytestmark = pytest.mark.skipif(not _HAVE_FFMPEG, reason="ffmpeg not installed")
diff --git a/packages/meshbay-node/tests/test_cli_dispatch.py b/packages/meshbay-node/tests/test_cli_dispatch.py
index 5b16fbe..8ef18c2 100644
--- a/packages/meshbay-node/tests/test_cli_dispatch.py
+++ b/packages/meshbay-node/tests/test_cli_dispatch.py
@@ -113,7 +113,7 @@ def stub_daemon(monkeypatch, tmp_path):
patch_cli(monkeypatch, "_resolve_group", lambda cfg, g: "g" * 32)
conf = tmp_path / "node.toml"
- conf.write_text('[hub]\nurl = "https://example.invalid"\n')
+ conf.write_text('[hub]\nurl = "https://example.invalid"\n', encoding="utf-8")
patch_cli(monkeypatch, "DEFAULT_CONFIG_PATH", conf)
# And a home of its own: the data directory (where `member invite` and
# `operator pair` write their code, where `status` finds the run token of
diff --git a/packages/meshbay-node/tests/test_cli_golden.py b/packages/meshbay-node/tests/test_cli_golden.py
index 3d7b0c0..5fe3f0b 100644
--- a/packages/meshbay-node/tests/test_cli_golden.py
+++ b/packages/meshbay-node/tests/test_cli_golden.py
@@ -18,11 +18,13 @@ import builtins
import getpass
import json
import os
+import re
import subprocess
import sys
from pathlib import Path
import pytest
+from meshbay_common import MNP_VERSION
from meshbay_node import config as config_mod
from meshbay_node import daemon as daemon_mod
from test_cli_dispatch import VERBS
@@ -77,7 +79,7 @@ def _run(argv: list[str], tmp_path: Path, capsys) -> dict:
return subprocess.CompletedProcess(args, 0, stdout="", stderr="")
conf = tmp_path / "node.toml"
- conf.write_text('[hub]\nurl = "https://example.invalid"\n')
+ conf.write_text('[hub]\nurl = "https://example.invalid"\n', encoding="utf-8")
code = 0
with pytest.MonkeyPatch.context() as mp:
patch_cli(mp, "_daemon_api", fake_api)
@@ -86,6 +88,9 @@ def _run(argv: list[str], tmp_path: Path, capsys) -> dict:
# The data directory and the keystore's default path come from these;
# left real, `status` would record the developer's own node.
mp.setenv("HOME", str(tmp_path / "home"))
+ # Path.home() on Windows: without it `member invite` and `operator
+ # pair` wrote their codes into the developer's own profile.
+ mp.setenv("USERPROFILE", str(tmp_path / "home"))
mp.setattr(config_mod, "DEFAULT_CONFIG_PATH", conf)
mp.setattr(builtins, "input", fake_input)
mp.setattr(getpass, "getpass", lambda *a, **kw: "test-password")
@@ -93,6 +98,9 @@ def _run(argv: list[str], tmp_path: Path, capsys) -> dict:
# argparse wraps the help to the terminal's width.
mp.setenv("COLUMNS", "100")
mp.setattr(sys, "argv", ["meshbay-node", *argv])
+ # Recorded on Linux: `autostart`/`service` are Windows-only there, and
+ # their Windows side is test_platform.py's and test_packaging_win.py's.
+ mp.setattr(sys, "platform", "linux")
try:
daemon_mod.main()
except SystemExit as e:
@@ -100,7 +108,17 @@ def _run(argv: list[str], tmp_path: Path, capsys) -> dict:
out = capsys.readouterr()
def mask(text: str) -> str:
- return text.replace(str(tmp_path), "<tmp>")
+ # Masked in the JSON text, where a Windows path's backslashes are
+ # doubled -- the unescaped form never matched there -- and spelled the
+ # POSIX way after it. The protocol version is not the CLI's shape: it
+ # left this recording failing everywhere from the MNP 4.0 bump on.
+ text = text.replace(json.dumps(str(tmp_path))[1:-1], "<tmp>")
+ text = re.sub(r'<tmp>(?:\\\\[^\\\s"]+)+',
+ lambda m: m.group(0).replace("\\\\", "/"), text)
+ # argparse quotes its choices on some Python versions and not others.
+ text = re.sub(r"\(choose from [^)]*\)",
+ lambda m: m.group(0).replace("'", ""), text)
+ return text.replace(f"MNP {MNP_VERSION}", "MNP <version>")
return json.loads(mask(json.dumps({
"exit": code, "stdout": out.out, "stderr": out.err,
diff --git a/packages/meshbay-node/tests/test_disk_io_off_loop.py b/packages/meshbay-node/tests/test_disk_io_off_loop.py
index a94789a..44864ba 100644
--- a/packages/meshbay-node/tests/test_disk_io_off_loop.py
+++ b/packages/meshbay-node/tests/test_disk_io_off_loop.py
@@ -47,6 +47,11 @@ GROUP = "g" * 32
# gets ~40 wake-ups inside one of these and a blocked one gets none.
SLOW_S = 0.2
TICK_S = 0.005
+# What a free loop actually achieves: Windows' asyncio timers run on the 15.6 ms
+# system tick, so a 5 ms sleep takes 15.6 and a free loop gets ~13 wake-ups,
+# never 40 -- every test here failed there against a threshold of 20. Still
+# unmistakable: a blocked loop gets none.
+FREE_TICKS = SLOW_S / max(TICK_S, time.get_clock_info("monotonic").resolution)
CONTENT = b"a file worth waking a disk for" * 400
@@ -139,7 +144,7 @@ async def test_a_slow_chunk_read_does_not_stop_the_loop(tmp_path, monkeypatch):
# The read really did take its time, and the loop really did keep running:
# both halves matter, because a wrapper that never ran would also leave the
# ticker free.
- assert ticker.ticks > SLOW_S / TICK_S / 2, (
+ assert ticker.ticks > FREE_TICKS / 2, (
f"the loop was blocked: {ticker.ticks} wake-ups during a {SLOW_S}s read")
chunk = next(m for m in session.sent if m.get("type") == MNP.FILE_CHUNK)
@@ -161,7 +166,7 @@ async def test_a_slow_stat_does_not_stop_the_loop(tmp_path, monkeypatch):
await session._do_file_request(
{"type": MNP.FILE_REQUEST, "file_id": entry.id, "chunk_index": 0})
- assert ticker.ticks > SLOW_S / TICK_S / 2, (
+ assert ticker.ticks > FREE_TICKS / 2, (
f"the loop was blocked: {ticker.ticks} wake-ups during a {SLOW_S}s stat")
assert any(m.get("type") == MNP.FILE_CHUNK for m in session.sent)
@@ -230,7 +235,7 @@ async def test_the_availability_poll_does_not_stop_the_loop(tmp_path, monkeypatc
await idx.stop()
roots.close_io()
- assert ticker.ticks > SLOW_S / TICK_S / 2, (
+ assert ticker.ticks > FREE_TICKS / 2, (
f"the loop was blocked: {ticker.ticks} wake-ups during a {SLOW_S}s poll")
@@ -322,7 +327,7 @@ async def test_a_slow_upload_write_does_not_stop_the_loop(tmp_path, monkeypatch)
await session._do_file_upload(sealed_upload(
session, filename="clip.bin", data=CONTENT))
- assert ticker.ticks > SLOW_S / TICK_S / 2, (
+ assert ticker.ticks > FREE_TICKS / 2, (
f"the loop was blocked: {ticker.ticks} wake-ups during a {SLOW_S}s write")
assert (shared / "clip.bin").read_bytes() == CONTENT
@@ -405,5 +410,5 @@ async def test_a_slow_scratch_read_does_not_stop_the_loop(tmp_path, monkeypatch)
media_tools._read_scratch_capped, scratch, 1 << 20, "subtitle track")
assert blob == CONTENT
- assert ticker.ticks > SLOW_S / TICK_S / 2, (
+ assert ticker.ticks > FREE_TICKS / 2, (
f"the loop was blocked: {ticker.ticks} wake-ups during a {SLOW_S}s read")
diff --git a/packages/meshbay-node/tests/test_hot_reload_survives_client_close.py b/packages/meshbay-node/tests/test_hot_reload_survives_client_close.py
index fb9eade..97c14d2 100644
--- a/packages/meshbay-node/tests/test_hot_reload_survives_client_close.py
+++ b/packages/meshbay-node/tests/test_hot_reload_survives_client_close.py
@@ -101,7 +101,7 @@ async def test_hot_loaded_group_finishes_scanning_without_anyone_awaiting_the_re
data_dir = tmp_path / "data"
config_path = tmp_path / "node.toml"
- config_path.write_text(_toml(data_dir, first_dir))
+ config_path.write_text(_toml(data_dir, first_dir), encoding="utf-8")
from meshbay_node.config import load_config
daemon = NodeDaemon(load_config(config_path), config_path=config_path)
@@ -163,7 +163,7 @@ async def test_hot_loaded_group_finishes_scanning_without_anyone_awaiting_the_re
# attach + /api/reload would leave it, then fire the reload exactly
# as ui/app.py does: scheduled, NOT awaited.
config_path.write_text(_toml(data_dir, first_dir,
- second_group_id, second_dir))
+ second_group_id, second_dir), encoding="utf-8")
reload_task = asyncio.ensure_future(daemon._reload_config())
# Stand in for "the browser tab is gone": do something completely
@@ -225,7 +225,7 @@ async def test_group_scoped_ops_404_until_listed_then_succeed(tmp_path):
data_dir = tmp_path / "data2"
config_path = tmp_path / "node2.toml"
- config_path.write_text(_toml(data_dir, first_dir))
+ config_path.write_text(_toml(data_dir, first_dir), encoding="utf-8")
from meshbay_node.config import load_config
daemon = NodeDaemon(load_config(config_path), config_path=config_path)
@@ -282,7 +282,7 @@ async def test_group_scoped_ops_404_until_listed_then_succeed(tmp_path):
# The same file write ops.attach_group does (a raw text append),
# then the same fire-and-forget reload /api/reload now does.
config_path.write_text(_toml(data_dir, first_dir,
- second_group_id, second_dir))
+ second_group_id, second_dir), encoding="utf-8")
reload_task = asyncio.ensure_future(ops.start_reload(daemon._state))
await asyncio.sleep(0.01)
diff --git a/packages/meshbay-node/tests/test_keystore.py b/packages/meshbay-node/tests/test_keystore.py
index b9dd3a4..5cef9f9 100644
--- a/packages/meshbay-node/tests/test_keystore.py
+++ b/packages/meshbay-node/tests/test_keystore.py
@@ -94,7 +94,7 @@ def test_env_var_unlock(tmp_path, monkeypatch):
def test_unlock_file(tmp_path):
path = tmp_path / "keystore.enc"
kf_path = tmp_path / "unlock.key"
- kf_path.write_text("filepass42")
+ kf_path.write_text("filepass42", encoding="utf-8")
kf_path.chmod(0o600)
create_keystore(path=path, password="filepass42")
diff --git a/packages/meshbay-node/tests/test_leaseless_reads.py b/packages/meshbay-node/tests/test_leaseless_reads.py
index 2c4c6cd..3c4018f 100644
--- a/packages/meshbay-node/tests/test_leaseless_reads.py
+++ b/packages/meshbay-node/tests/test_leaseless_reads.py
@@ -108,7 +108,7 @@ def test_the_bound_covers_what_the_music_player_actually_reads_ahead():
player = SPA / "music-player.js"
if not player.exists():
pytest.skip("the SPA sources are not present next to the node package")
- body = player.read_text()
+ body = player.read_text(encoding="utf-8")
fn = body[body.index("function prefetchDepth()"):]
fn = fn[:fn.index("\n}\n")]
depths = [int(n) for n in re.findall(r"return (\d+);", fn)]
diff --git a/packages/meshbay-node/tests/test_node_status.py b/packages/meshbay-node/tests/test_node_status.py
index 8bb2f39..bd63f8e 100644
--- a/packages/meshbay-node/tests/test_node_status.py
+++ b/packages/meshbay-node/tests/test_node_status.py
@@ -266,7 +266,7 @@ async def test_add_root_creates_directory_and_returns_info(tmp_path):
conf = tmp_path / "node.toml"
conf.write_text(f'[[groups]]\nid = "{GROUP}"\nname = "test"\n\n'
- f' [[groups.roots]]\n path = "{shared}"\n')
+ f' [[groups.roots]]\n path = "{shared}"\n', encoding="utf-8")
node_cfg = NodeConfig.__new__(NodeConfig)
node_cfg.groups = [cfg]
@@ -308,7 +308,7 @@ async def test_remove_root_requires_at_least_one_remaining(tmp_path):
conf = tmp_path / "node.toml"
conf.write_text(f'[[groups]]\nid = "{GROUP}"\nname = "test"\n\n'
- f' [[groups.roots]]\n path = "{shared}"\n')
+ f' [[groups.roots]]\n path = "{shared}"\n', encoding="utf-8")
index = GroupIndex(group_id=GROUP, sk_node=Ed25519PrivateKey.generate())
roots = one_root(shared)
state = {
@@ -344,7 +344,7 @@ async def test_removing_a_writable_root_is_allowed(tmp_path):
conf.write_text(
f'[[groups]]\nid = "{GROUP}"\nname = "test"\n\n'
f' [[groups.roots]]\n path = "{d1}"\n name = "incoming"\n writable = true\n\n'
- f' [[groups.roots]]\n path = "{d2}"\n name = "shared"\n')
+ f' [[groups.roots]]\n path = "{d2}"\n name = "shared"\n', encoding="utf-8")
roots = RootSet.build([asdict(r) for r in cfg.roots])
index = GroupIndex(group_id=GROUP, sk_node=Ed25519PrivateKey.generate())
state = {
@@ -355,7 +355,7 @@ async def test_removing_a_writable_root_is_allowed(tmp_path):
result = await ops.remove_root(state, GROUP, "incoming")
assert result["status"] == "removed"
assert [r["name"] for r in result["roots"]] == ["shared"]
- assert conf.read_text().count("[[groups.roots]]") == 1
+ assert conf.read_text(encoding="utf-8").count("[[groups.roots]]") == 1
async def test_update_root_rewrites_the_flags_in_node_toml(tmp_path):
@@ -380,7 +380,7 @@ async def test_update_root_rewrites_the_flags_in_node_toml(tmp_path):
f'[[groups]]\nid = "{GROUP}"\nname = "test"\n\n'
f' [[groups.roots]]\n'
f' # the operator explained this one to themselves\n'
- f' path = "{d1}"\n name = "media"\n')
+ f' path = "{d1}"\n name = "media"\n', encoding="utf-8")
roots = RootSet.build([asdict(r) for r in cfg.roots])
index = GroupIndex(group_id=GROUP, sk_node=Ed25519PrivateKey.generate())
state = {
@@ -392,7 +392,7 @@ async def test_update_root_rewrites_the_flags_in_node_toml(tmp_path):
result = await ops.update_root(state, GROUP, "media",
writable=True, removable=True)
assert result["status"] == "updated"
- text = conf.read_text()
+ text = conf.read_text(encoding="utf-8")
assert "writable = true" in text
assert "removable = true" in text
assert "the operator explained this one to themselves" in text, (
@@ -406,7 +406,7 @@ async def test_update_root_rewrites_the_flags_in_node_toml(tmp_path):
# A second call that changes nothing must not append a duplicate line.
await ops.update_root(state, GROUP, "media", writable=True, removable=True)
- assert conf.read_text().count("writable =") == 1
+ assert conf.read_text(encoding="utf-8").count("writable =") == 1
async def test_update_root_replaces_a_legacy_upload_line(tmp_path):
@@ -430,7 +430,7 @@ async def test_update_root_replaces_a_legacy_upload_line(tmp_path):
conf.write_text(
f'[[groups]]\nid = "{GROUP}"\nname = "test"\n\n'
f' [[groups.roots]]\n path = "{d1}"\n name = "media"\n'
- f' upload = true\n')
+ f' upload = true\n', encoding="utf-8")
roots = RootSet.build([asdict(r) for r in cfg.roots])
index = GroupIndex(group_id=GROUP, sk_node=Ed25519PrivateKey.generate())
state = {
@@ -440,7 +440,7 @@ async def test_update_root_replaces_a_legacy_upload_line(tmp_path):
}
await ops.update_root(state, GROUP, "media", writable=False)
- text = conf.read_text()
+ text = conf.read_text(encoding="utf-8")
assert "upload = true" not in text
assert "writable = false" in text
@@ -463,7 +463,7 @@ async def test_remove_root_succeeds_with_two_roots(tmp_path):
conf.write_text(
f'[[groups]]\nid = "{GROUP}"\nname = "test"\n\n'
f' [[groups.roots]]\n path = "{d1}"\n name = "dir1"\n\n'
- f' [[groups.roots]]\n path = "{d2}"\n name = "dir2"\n')
+ f' [[groups.roots]]\n path = "{d2}"\n name = "dir2"\n', encoding="utf-8")
roots = RootSet.build([asdict(r) for r in cfg.roots])
index = GroupIndex(group_id=GROUP, sk_node=Ed25519PrivateKey.generate())
state = {
diff --git a/packages/meshbay-node/tests/test_ops.py b/packages/meshbay-node/tests/test_ops.py
index d49d3fa..b011802 100644
--- a/packages/meshbay-node/tests/test_ops.py
+++ b/packages/meshbay-node/tests/test_ops.py
@@ -145,7 +145,7 @@ async def test_deleting_a_file_removes_it_from_disk_and_index(tmp_path):
state = _state(tmp_path)
ctx = state["groups_ctx"]["g" * 32]
target = ctx["roots"].roots[0].path / "gone.txt"
- target.write_text("x")
+ target.write_text("x", encoding="utf-8")
from meshbay_common.protocol import IndexEntry
ctx["index"].add_entry(IndexEntry(id="a" * 64, name="gone.txt", path="shared",
size=1, type="other", added_at=0))
diff --git a/packages/meshbay-node/tests/test_quic_enabled.py b/packages/meshbay-node/tests/test_quic_enabled.py
index c0c1568..b3abd8e 100644
--- a/packages/meshbay-node/tests/test_quic_enabled.py
+++ b/packages/meshbay-node/tests/test_quic_enabled.py
@@ -15,7 +15,7 @@ from meshbay_node.config import load_config
def _cfg(tmp_path: Path, body: str):
p = tmp_path / "node.toml"
- p.write_text(textwrap.dedent(body))
+ p.write_text(textwrap.dedent(body), encoding="utf-8")
return load_config(p)
diff --git a/packages/meshbay-node/tests/test_root_ops_reach_the_live_set.py b/packages/meshbay-node/tests/test_root_ops_reach_the_live_set.py
index e90fdfe..b2b3650 100644
--- a/packages/meshbay-node/tests/test_root_ops_reach_the_live_set.py
+++ b/packages/meshbay-node/tests/test_root_ops_reach_the_live_set.py
@@ -58,7 +58,7 @@ async def _state(tmp_path: Path) -> tuple[dict, Roster]:
f' [[groups.roots]]\n path = "{(tmp_path / "one").as_posix()}"\n'
f' name = "one"\n\n'
f' [[groups.roots]]\n path = "{(tmp_path / "two").as_posix()}"\n'
- f' name = "two"\n')
+ f' name = "two"\n', encoding="utf-8")
live = RootSet.build([asdict(r) for r in cfg.roots])
index = GroupIndex(group_id=GROUP, sk_node=Ed25519PrivateKey.generate())
@@ -101,7 +101,7 @@ async def test_adding_a_root_reaches_node_toml_and_the_ack(tmp_path):
assert [r["name"] for r in result["roots"]] == ["one", "two", "uploads"]
assert [r.name for r in state["config"].groups[0].roots] == [
"one", "two", "uploads"]
- assert "uploads" in Path(state["config_path"]).read_text()
+ assert "uploads" in Path(state["config_path"]).read_text(encoding="utf-8")
finally:
await roster.close()
@@ -111,7 +111,7 @@ async def test_removing_a_root_reaches_node_toml_and_the_ack(tmp_path):
try:
result = await ops.remove_root(state, GROUP, "two")
assert [r["name"] for r in result["roots"]] == ["one"]
- assert Path(state["config_path"]).read_text().count(
+ assert Path(state["config_path"]).read_text(encoding="utf-8").count(
"[[groups.roots]]") == 1
finally:
await roster.close()
@@ -229,7 +229,7 @@ async def test_adding_the_same_directory_twice_is_still_refused(tmp_path):
await ops.add_root(state, GROUP, str(tmp_path / "uploads"))
assert len(state["config"].groups[0].roots) == 3, (
"the refused add left something behind")
- assert Path(state["config_path"]).read_text().count(
+ assert Path(state["config_path"]).read_text(encoding="utf-8").count(
"[[groups.roots]]") == 3
finally:
await roster.close()
@@ -283,12 +283,12 @@ async def test_the_file_on_disk_and_the_config_in_memory_agree(tmp_path):
await ops.remove_root(state, GROUP, "one")
import tomllib
- on_disk = tomllib.loads(Path(state["config_path"]).read_text())
+ on_disk = tomllib.loads(Path(state["config_path"]).read_text(encoding="utf-8"))
disk_paths = [str(r["path"]) for r in on_disk["groups"][0]["roots"]]
memory_paths = [Path(r.path).as_posix()
for r in state["config"].groups[0].roots]
assert disk_paths == memory_paths
- assert Path(state["config_path"]).read_text().count(
+ assert Path(state["config_path"]).read_text(encoding="utf-8").count(
"[[groups.roots]]") == 2
finally:
await roster.close()
diff --git a/packages/meshbay-node/tests/test_root_paths_are_operator_only.py b/packages/meshbay-node/tests/test_root_paths_are_operator_only.py
index e00f1cf..646c7b4 100644
--- a/packages/meshbay-node/tests/test_root_paths_are_operator_only.py
+++ b/packages/meshbay-node/tests/test_root_paths_are_operator_only.py
@@ -122,7 +122,7 @@ def test_the_cli_only_reads_fields_the_payload_carries(monkeypatch, tmp_path, ca
"groups": [{"id": gid, "roots": [_Recording(offered)]}]})
patch_cli(monkeypatch, "_resolve_group", lambda cfg, g: gid)
conf = tmp_path / "node.toml"
- conf.write_text('[hub]\nurl = "https://example.invalid"\n')
+ conf.write_text('[hub]\nurl = "https://example.invalid"\n', encoding="utf-8")
patch_cli(monkeypatch, "DEFAULT_CONFIG_PATH", conf)
# A stub missed would otherwise reach the node running on this machine.
monkeypatch.setenv("HOME", str(tmp_path / "home"))
@@ -131,7 +131,7 @@ def test_the_cli_only_reads_fields_the_payload_carries(monkeypatch, tmp_path, ca
daemon_mod.main()
assert read, "the root CLI no longer reads the payload this way"
- assert "/tmp/x" in capsys.readouterr().out
+ assert str(Path("/tmp/x")) in capsys.readouterr().out
assert read <= set(offered), (
f"the `root` CLI reads keys the loopback payload does not carry: "
f"{sorted(read - set(offered))}")
diff --git a/packages/meshbay-node/tests/test_root_work_outlives_the_session.py b/packages/meshbay-node/tests/test_root_work_outlives_the_session.py
index 1e31c31..92bea4d 100644
--- a/packages/meshbay-node/tests/test_root_work_outlives_the_session.py
+++ b/packages/meshbay-node/tests/test_root_work_outlives_the_session.py
@@ -60,7 +60,7 @@ async def _daemon(tmp_path: Path, one: Path, two: Path):
f'data_dir = "{(tmp_path / "data").as_posix()}"\n\n'
f'[[groups]]\nid = "{GROUP}"\nname = "plop"\n\n'
f' [[groups.roots]]\n path = "{one.as_posix()}"\n name = "one"\n\n'
- f' [[groups.roots]]\n path = "{two.as_posix()}"\n name = "two"\n')
+ f' [[groups.roots]]\n path = "{two.as_posix()}"\n name = "two"\n', encoding="utf-8")
idx = DirectoryIndexer(roots=_set(one), group_id=GROUP,
sk_node=Ed25519PrivateKey.generate(), gek=None)
diff --git a/packages/meshbay-node/tests/test_roots.py b/packages/meshbay-node/tests/test_roots.py
index 004f004..9a403ff 100644
--- a/packages/meshbay-node/tests/test_roots.py
+++ b/packages/meshbay-node/tests/test_roots.py
@@ -320,13 +320,13 @@ def test_free_name_returns_original_when_not_taken(tmp_path):
def test_free_name_appends_counter_on_collision(tmp_path):
- (tmp_path / "photo.jpg").write_text("x")
+ (tmp_path / "photo.jpg").write_text("x", encoding="utf-8")
assert _free_name(tmp_path, "photo.jpg") == "photo (2).jpg"
def test_free_name_increments_past_multiple_collisions(tmp_path):
- (tmp_path / "photo.jpg").write_text("x")
- (tmp_path / "photo (2).jpg").write_text("x")
+ (tmp_path / "photo.jpg").write_text("x", encoding="utf-8")
+ (tmp_path / "photo (2).jpg").write_text("x", encoding="utf-8")
assert _free_name(tmp_path, "photo.jpg") == "photo (3).jpg"
diff --git a/packages/meshbay-node/tests/test_roster_pairing.py b/packages/meshbay-node/tests/test_roster_pairing.py
index a2d0f8b..5c94855 100644
--- a/packages/meshbay-node/tests/test_roster_pairing.py
+++ b/packages/meshbay-node/tests/test_roster_pairing.py
@@ -634,7 +634,7 @@ def test_code_lifetimes_are_configurable(tmp_path):
path.write_text(
'[hub]\nurl = "https://example.org"\nusername = "grenet"\n'
"[node]\ninvite_ttl_hours = 72\npair_ttl_hours = 2\n"
- )
+ , encoding="utf-8")
cfg = load_config(path)
assert cfg.node.invite_ttl_hours == 72
assert cfg.node.pair_ttl_hours == 2
@@ -924,7 +924,7 @@ async def test_group_add_appends_without_rewriting_the_file(tmp_path):
conf = tmp_path / "node.toml"
conf.write_text(
'# keep me\n[hub]\nurl = "https://meshbay.org"\nusername = "grenet"\n\n'
- '[[groups]]\nid = "aaaa"\nname = "first"\nshared_dir = "/tmp/a"\n')
+ '[[groups]]\nid = "aaaa"\nname = "first"\nshared_dir = "/tmp/a"\n', encoding="utf-8")
block = ('\n[[groups]]\n'
'id = "bbbb"\n'
@@ -974,7 +974,7 @@ async def test_a_directory_with_anything_in_it_is_refused(tmp_path, roster):
session = await _dir_session(tmp_path, roster)
full = tmp_path / "shared" / "full"
full.mkdir()
- (full / "keep.txt").write_text("still here")
+ (full / "keep.txt").write_text("still here", encoding="utf-8")
await session._do_dir_delete({"dir": "shared/full"})
diff --git a/packages/meshbay-node/tests/test_security_regressions.py b/packages/meshbay-node/tests/test_security_regressions.py
index 8e90332..c0c2c2c 100644
--- a/packages/meshbay-node/tests/test_security_regressions.py
+++ b/packages/meshbay-node/tests/test_security_regressions.py
@@ -682,7 +682,7 @@ def test_legacy_keystore_still_opens(tmp_path):
"iv_b64": _b64.b64encode(iv).decode(),
"tag_b64": _b64.b64encode(tag).decode(),
"ciphertext_b64": _b64.b64encode(ct).decode(),
- }))
+ }), encoding="utf-8")
keys = load_keystore(path=path, password="legacy-pass")
assert keys.pk_ed25519_b64 == pk_to_b64(sk_ed.public_key())
diff --git a/packages/meshbay-node/tests/test_stream_capacity_config.py b/packages/meshbay-node/tests/test_stream_capacity_config.py
index a40cbc0..9350b4c 100644
--- a/packages/meshbay-node/tests/test_stream_capacity_config.py
+++ b/packages/meshbay-node/tests/test_stream_capacity_config.py
@@ -32,7 +32,7 @@ from node_source import daemon_call
def _cfg(tmp_path: Path, body: str):
p = tmp_path / "node.toml"
- p.write_text(textwrap.dedent(body))
+ p.write_text(textwrap.dedent(body), encoding="utf-8")
return load_config(p)
diff --git a/packages/meshbay-node/tests/test_upload_size_cap.py b/packages/meshbay-node/tests/test_upload_size_cap.py
index d2deb03..677edde 100644
--- a/packages/meshbay-node/tests/test_upload_size_cap.py
+++ b/packages/meshbay-node/tests/test_upload_size_cap.py
@@ -26,7 +26,7 @@ from node_source import daemon_call, webrtc_source
def _cfg(tmp_path: Path, body: str):
p = tmp_path / "node.toml"
- p.write_text(textwrap.dedent(body))
+ p.write_text(textwrap.dedent(body), encoding="utf-8")
return load_config(p)