aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/test_downloads.py
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/meshbay-hub/tests/test_downloads.py
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/meshbay-hub/tests/test_downloads.py')
-rw-r--r--packages/meshbay-hub/tests/test_downloads.py54
1 files changed, 27 insertions, 27 deletions
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)