85 lines
4.7 KiB
Python
85 lines
4.7 KiB
Python
"""Real staged publication, stale-update rejection and live PyPI wheels, no registry changes."""
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
PROJECT = Path(__file__).resolve().parent.parent
|
|
|
|
|
|
def inside(packages=False):
|
|
from host.bundle import ROOT, installation_root
|
|
from host.updates import update
|
|
from host.media import capabilities
|
|
application = installation_root()
|
|
marker = application / "installation.json"
|
|
before = json.loads(marker.read_text(encoding="utf-8-sig"))["current"]
|
|
if packages:
|
|
import io
|
|
import time
|
|
from host.main import NativeHost
|
|
host = NativeHost(io.BytesIO())
|
|
try:
|
|
response = host.start_update([{"id": "packages", "version": "latest-compatible"}])
|
|
deadline = time.monotonic() + 500
|
|
while host.jobs[response["jobId"]]["status"] == "running" and time.monotonic() < deadline: time.sleep(0.1)
|
|
job = host.jobs[response["jobId"]]
|
|
assert job["status"] == "complete", job
|
|
result = job["result"]
|
|
finally: host.close()
|
|
after = Path(json.loads(marker.read_text(encoding="utf-8-sig"))["current"])
|
|
versions = json.loads((after / "bundle.json").read_text(encoding="utf-8"))["pythonPackages"]
|
|
assert result["restartRequired"] and "yt-dlp" in {key.lower() for key in versions}
|
|
print(json.dumps({"livePyPI": "passed", "nativeWorkerUpdate": "passed", "packages": versions}))
|
|
return
|
|
archive = PROJECT / ".build-cache/node.zip"
|
|
data = archive.read_bytes()
|
|
candidate = {"id": "node", "version": "v24.21.0", "url": "https://nodejs.org/dist/v24.21.0/node-v24.21.0-win-x64.zip", "sha256": hashlib.sha256(data).hexdigest()}
|
|
class Response:
|
|
headers = {"Content-Length": str(len(data))}
|
|
def __enter__(self): return self
|
|
def __exit__(self, *args): return False
|
|
def iter_content(self, count):
|
|
for offset in range(0, len(data), count): yield data[offset:offset + count]
|
|
dll = ROOT / "runtime/python/python313.dll"
|
|
with dll.open("rb") as opened, patch("host.updates.get", return_value=Response()):
|
|
result = update(lambda *args: None, lambda _: None, [candidate])
|
|
after = json.loads(marker.read_text(encoding="utf-8-sig"))["current"]
|
|
assert after != before and result["restartRequired"] and dll.is_file() and opened.read(2) == b"MZ"
|
|
try: update(lambda *args: None, lambda _: None, [candidate])
|
|
except ValueError as error: assert "другой установкой" in str(error)
|
|
else: raise AssertionError("A stale helper must not replace the newer runtime")
|
|
try: update(lambda *args: None, lambda _: None, [{**candidate, "sha256": "0" * 64}])
|
|
except ValueError as error: assert "Контрольная сумма" in str(error)
|
|
else: raise AssertionError("Modified downloads must be rejected")
|
|
assert json.loads(marker.read_text(encoding="utf-8-sig"))["current"] == after
|
|
assert not list((application / "releases").glob(".ytdl-update-*")) == [], "The published release must remain"
|
|
print(json.dumps({"stagedActivation": "passed", "openOldDLL": "preserved", "staleUpdate": "rejected", "badHash": "rejected"}))
|
|
|
|
|
|
def outer():
|
|
artifacts = PROJECT / "test-artifacts"; artifacts.mkdir(exist_ok=True)
|
|
with tempfile.TemporaryDirectory(prefix="update-test-", dir=artifacts) as temporary:
|
|
root = Path(temporary) / "Helper"
|
|
process = subprocess.run([str(PROJECT / "dist/YouTubeDL-Helper-Setup.exe"), "--silent", "--no-register", "--target-root", str(root)], capture_output=True, text=True, encoding="utf-8", timeout=180)
|
|
assert process.returncode == 0, process.stdout + process.stderr
|
|
records = []
|
|
for action in ("--inside", "--inside-packages"):
|
|
marker = json.loads((root / "installation.json").read_text(encoding="utf-8-sig"))
|
|
python = Path(marker["current"]) / "runtime/python/python.exe"
|
|
result = subprocess.run([str(python), "-I", "-X", "utf8", "-B", str(Path(__file__).resolve()), action], capture_output=True, text=True, encoding="utf-8", timeout=600)
|
|
assert result.returncode == 0, result.stdout[-1000:] + result.stderr[-4000:]
|
|
records.append(json.loads(result.stdout.splitlines()[-1]))
|
|
(artifacts / "component-update.json").write_text(json.dumps(records, indent=2), encoding="utf-8")
|
|
print("Component updates passed: live PyPI downloads, verified stage activation, retained old DLL, bad hash rejection and stale-update rejection.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if "--inside" in sys.argv: inside()
|
|
elif "--inside-packages" in sys.argv: inside(True)
|
|
else: outer()
|