Files
YouTubeDL/tests/offline-bundle.py
T
DepressedCat 5f2aad00f4
Check extension and helper / check (push) Canceled after 0s
Publish YouTubeDL 1.4.5 and website on Gitea
2026-10-10 23:53:17 +03:00

98 lines
6.0 KiB
Python

"""Exercise the real offline installer with unusable external tools and proxies."""
import ctypes
import json
import os
import subprocess
import tempfile
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
ARTIFACTS = ROOT / "test-artifacts"
ARTIFACTS.mkdir(exist_ok=True)
INSTALLER = ROOT / "dist/YouTubeDL-Helper-Setup.exe"
environment = {**os.environ, "PATH": str(Path(os.environ.get("SystemRoot", r"C:\Windows")) / "System32"),
"PYTHONHOME": "C:/NONEXISTENT", "PYTHONPATH": "C:/NONEXISTENT", "PYTHONNOUSERSITE": "1",
"HTTP_PROXY": "http://127.0.0.1:9", "HTTPS_PROXY": "http://127.0.0.1:9", "ALL_PROXY": "http://127.0.0.1:9"}
def run(*args):
result = subprocess.run([str(item) for item in args], env=environment, capture_output=True,
encoding="utf-8", errors="replace", timeout=180)
if result.returncode:
raise RuntimeError(json.dumps({"code": result.returncode, "stdout": result.stdout[-500:], "stderr": result.stderr[-2500:]}))
return result
def picker(manager, folder):
import base64
encoded = base64.b64encode(json.dumps(str(folder)).encode()).decode()
process = subprocess.Popen([str(manager), "--pick-folder", "--initial-folder", encoded], env=environment,
stdout=subprocess.PIPE, stderr=subprocess.PIPE, creationflags=subprocess.CREATE_NO_WINDOW)
user32 = ctypes.windll.user32
callback_type = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.c_void_p, ctypes.c_void_p)
dialogs = []
def callback(window, unused):
pid = ctypes.c_ulong(); user32.GetWindowThreadProcessId(window, ctypes.byref(pid))
if pid.value == process.pid:
name = ctypes.create_unicode_buffer(256); user32.GetClassNameW(window, name, 256)
if name.value == "#32770" and user32.IsWindowVisible(window): dialogs.append(window)
return True
deadline = time.monotonic() + 15
handler = callback_type(callback)
while not dialogs and time.monotonic() < deadline:
user32.EnumWindows(handler, 0); time.sleep(0.1)
assert dialogs, "The bundled Windows folder picker must open"
# Select its preselected existing folder, including Unicode in the path.
user32.PostMessageW(ctypes.c_void_p(dialogs[0]), 0x0111, 1, 0) # WM_COMMAND / IDOK
output, errors = process.communicate(timeout=15)
assert process.returncode == 0, errors.decode("utf-8", errors="replace")
assert Path(json.loads(output)) == folder
with tempfile.TemporaryDirectory(prefix="offline-bundle-", dir=ARTIFACTS) as temporary:
parent = Path(temporary)
application = parent / "Helper"
legacy = application / ".runtime"; legacy.mkdir(parents=True)
(legacy / "completed-jobs.json").write_text("[]")
first = run(INSTALLER, "--silent", "--no-register", "--target-root", application)
assert (application / "data/completed-jobs.json").read_text() == "[]"
marker = json.loads((application / "installation.json").read_text(encoding="utf-8-sig"))
release = Path(marker["current"])
python = release / "runtime/python/python.exe"
measured = json.loads(run(python, "-I", "-B", "-c",
"import json,sys; from host.media import capabilities,tool; print(json.dumps({'python':sys.executable,'path':sys.path,'hardware':capabilities(),'tools':{k:tool(k) for k in ('node','ffmpeg','ffprobe')}}))").stdout)
assert measured["hardware"]["ready"]
assert measured["hardware"]["bundle"]["mode"] == "autonomous"
assert {"h264", "hevc", "av1"} <= set(measured["hardware"]["nvenc"]), measured["hardware"]
assert Path(measured["python"]).is_relative_to(release)
assert all(Path(path).is_relative_to(release) for path in measured["tools"].values())
assert all(Path(path).is_relative_to(release) for path in measured["path"])
picker(application / "YouTubeDL-Manager.exe", ARTIFACTS)
preserved = release / "user-kept.mp4"; preserved.write_bytes(b"user file")
history = application / "data/completed-jobs.json"; history.parent.mkdir(exist_ok=True); history.write_text("[]")
# Reinstall beside the running copy; an old DLL handle must not prevent repair.
handle = (release / "runtime/python/python313.dll").open("rb")
try: run(INSTALLER, "--silent", "--no-register", "--target-root", application)
finally: handle.close()
current = Path(json.loads((application / "installation.json").read_text(encoding="utf-8-sig"))["current"])
assert current != release and current.is_dir()
manager = application / "YouTubeDL-Manager.exe"
run(manager, "--uninstall", "--silent", "--target-root", application)
assert preserved.read_bytes() == b"user file" and history.read_text() == "[]"
assert not current.exists() and not python.exists()
deadline = time.monotonic() + 15
while manager.exists() and time.monotonic() < deadline: time.sleep(0.1)
assert not manager.exists(), "The uninstaller must remove its own installed executable"
# Payload damage must be rejected before registering or installing any tools.
damaged = parent / "damaged.exe"
payload = bytearray(INSTALLER.read_bytes()); offset = payload.index(b"PK\x03\x04"); payload[offset + 100] ^= 1
damaged.write_bytes(payload)
invalid = subprocess.run([str(damaged), "--silent", "--no-register", "--target-root", str(parent / "Invalid")],
env=environment, capture_output=True, encoding="utf-8", timeout=60)
assert invalid.returncode != 0 and "Контрольная сумма" in invalid.stderr, invalid.stderr
(ARTIFACTS / "offline-bundle.json").write_text(json.dumps({"offlineInstallation": "passed", "isolatedRuntime": "passed",
"folderPicker": "passed", "repairWhileOldFilesOpen": "passed", "uninstallPreservesUserMedia": "passed", "corruptedPayload": "rejected",
"versions": measured["hardware"]["dependencies"], "nvenc": measured["hardware"]["nvenc"]}, ensure_ascii=False, indent=2), encoding="utf-8")
print("Offline bundle passed: isolated runtime, blocked proxies, Windows picker, NVENC, repair, scoped uninstall and damaged-payload rejection.")