Release 1.1.3 with game window and Gamescope lifecycle fixes

This commit is contained in:
2026-10-25 20:47:19 +01:00
parent b6de80e758
commit 1f2aad0ee2
42 changed files with 9291 additions and 153 deletions

View File

@@ -0,0 +1,866 @@
from __future__ import annotations
import hashlib
import json
import os
from pathlib import Path
import shutil
import shlex
import subprocess
import sys
import tarfile
import tempfile
import unittest
from ptlinux import __version__
from ptlinux.desktop_entry import escape_icon_path, quote_exec_argument
from ptlinux.launcher_wrapper import quote_shell_word
from scripts.repository_paths import PROJECT_ROOT, SOURCE_ROOT
from tools.sign_release_artifact import sign_release_artifact
class AioPackagingTests(unittest.TestCase):
def test_bootstrap_release_remains_reproducible_with_a_fixed_epoch(self) -> None:
with tempfile.TemporaryDirectory() as temporary_directory:
root = Path(temporary_directory)
outputs = (root / "first", root / "second")
for output in outputs:
environment = os.environ.copy()
environment.pop("PTLINUX_VERSION", None)
environment.update(
{
"PTLINUX_OUTPUT_DIRECTORY": str(output),
"PTLINUX_PYTHON": sys.executable,
"SOURCE_DATE_EPOCH": "0",
}
)
built = subprocess.run(
["bash", str(PROJECT_ROOT / "tools/build-aio-release.sh")],
cwd=PROJECT_ROOT,
env=environment,
capture_output=True,
text=True,
timeout=30,
check=False,
)
self.assertEqual(built.returncode, 0, built.stderr)
first = {
path.name: path.read_bytes()
for path in outputs[0].iterdir()
if path.is_file()
}
second = {
path.name: path.read_bytes()
for path in outputs[1].iterdir()
if path.is_file()
}
self.assertEqual(first, second)
def test_packaged_installer_uses_the_compatible_top_level_source_layout(self) -> None:
with tempfile.TemporaryDirectory() as temporary_directory:
root = Path(temporary_directory)
output = root / "dist"
build_environment = os.environ.copy()
build_environment.pop("PTLINUX_VERSION", None)
build_environment.update(
{
"PTLINUX_OUTPUT_DIRECTORY": str(output),
"PTLINUX_PYTHON": sys.executable,
"SOURCE_DATE_EPOCH": "0",
}
)
built = subprocess.run(
["bash", str(PROJECT_ROOT / "tools/build-aio-release.sh")],
cwd=PROJECT_ROOT,
env=build_environment,
capture_output=True,
text=True,
timeout=30,
check=False,
)
self.assertEqual(built.returncode, 0, built.stderr)
release_name = (
f"pristontale-eu-linux-installer-v{__version__.replace('.', '-')}"
)
archive = output / f"{release_name}.tar.gz"
extracted = root / "extracted"
with tarfile.open(archive, "r:gz") as bundle:
bundle.extractall(extracted, filter="data")
packaged_root = extracted / release_name
self.assertTrue((packaged_root / "ptlinux").is_dir())
self.assertFalse((packaged_root / "src").exists())
home = root / "home"
data_home = root / "desktop-data"
install_directory = root / "managed-install"
install_environment = os.environ.copy()
install_environment.pop("PTLINUX_INSTALL_DIR", None)
install_environment.update(
{
"HOME": str(home),
"XDG_DATA_HOME": str(data_home),
"PATH": "/usr/bin:/bin",
"PTLINUX_PYTHON": sys.executable,
}
)
installed = subprocess.run(
[
"bash",
str(packaged_root / "install.sh"),
"--install-dir",
str(install_directory),
"--skip-deps",
"--skip-doctor",
"--skip-runtime-bootstrap",
],
cwd=packaged_root,
env=install_environment,
capture_output=True,
text=True,
timeout=30,
check=False,
)
self.assertEqual(installed.returncode, 0, installed.stderr)
current = install_directory / "app/current"
self.assertTrue((current / "ptlinux/cli.py").is_file())
self.assertTrue((current / "ptdiag/logs.py").is_file())
def test_installer_dry_run_reports_destinations_without_writing(self) -> None:
with tempfile.TemporaryDirectory() as temporary_directory:
root = Path(temporary_directory)
home = root / "home"
data_home = root / "desktop-data"
install_directory = root / "managed-install"
environment = os.environ.copy()
environment.pop("PTLINUX_INSTALL_DIR", None)
environment.update(
{
"HOME": str(home),
"XDG_DATA_HOME": str(data_home),
"PATH": "/usr/bin:/bin",
"PTLINUX_PYTHON": sys.executable,
}
)
result = subprocess.run(
[
"bash",
str(PROJECT_ROOT / "install.sh"),
"--install-dir",
str(install_directory),
"--dry-run",
"--skip-deps",
"--skip-doctor",
"--skip-runtime-bootstrap",
],
cwd=PROJECT_ROOT,
env=environment,
capture_output=True,
text=True,
timeout=10,
check=False,
)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn(f"Install data: {install_directory}", result.stdout)
self.assertIn("Skipping host dependency installation.", result.stdout)
self.assertFalse(install_directory.exists())
self.assertFalse(home.exists())
self.assertFalse(data_home.exists())
def test_installer_writes_path_independent_desktop_entry(self) -> None:
with tempfile.TemporaryDirectory() as temporary_directory:
root = Path(temporary_directory)
home = root / "home with spaces"
data_home = root / "data with spaces"
home.mkdir()
fake_bin = root / "bin"
fake_bin.mkdir()
fake_umu = fake_bin / "umu-run"
fake_umu.write_text("#!/bin/sh\nexit 0\n", encoding="utf-8")
fake_umu.chmod(0o755)
environment = os.environ.copy()
environment.pop("PTLINUX_INSTALL_DIR", None)
environment.update(
{
"HOME": str(home),
"XDG_DATA_HOME": str(data_home),
"PATH": f"{fake_bin}:/usr/bin:/bin",
"PTLINUX_PYTHON": sys.executable,
}
)
result = subprocess.run(
[
"bash",
str(PROJECT_ROOT / "install.sh"),
"--skip-deps",
"--skip-doctor",
],
cwd=PROJECT_ROOT,
env=environment,
capture_output=True,
text=True,
timeout=30,
check=False,
)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn(str(fake_umu.resolve()), result.stdout)
self.assertIn(
f"Note: {home / '.local/bin'} is not in PATH for this shell.",
result.stdout,
)
output_lines = result.stdout.splitlines()
start_line = output_lines[output_lines.index("Start PristonTale EU with:") + 1]
self.assertEqual(
shlex.split(start_line),
[str(home / ".local/bin/pristontale-eu"), "gui"],
)
self.assertIn("export PATH=", result.stdout)
default_install = home / "Games/PristontaleEU"
self.assertTrue((default_install / "app/current/ptlinux").is_dir())
self.assertTrue((default_install / "app/current/ptlinux/umu.py").is_file())
self.assertTrue(
(default_install / "app/current/ptlinux/ui/dashboard.py").is_file()
)
self.assertTrue(
(
default_install
/ "app/current/ptlinux/ui/viewmodels/downloader.py"
).is_file()
)
self.assertTrue(
(default_install / "app/current/ptlinux/native/pt-identity-probe.exe").is_file()
)
self.assertTrue(
(default_install / "app/current/ptlinux/native/ptr-bcrypt-proxy.dll").is_file()
)
gamescope = (
default_install
/ "app/current/ptlinux/native/gamescope-3.16.20-ubuntu26.04-amd64"
)
self.assertTrue(gamescope.is_file())
self.assertEqual(gamescope.stat().st_mode & 0o777, 0o755)
self.assertFalse(
(default_install / "app/current/ptlinux/desktop_entry.py").exists()
)
self.assertFalse(
(default_install / "app/current/ptlinux/launcher_wrapper.py").exists()
)
self.assertFalse(
(default_install / "app/current/ptlinux/world_fix.py").exists()
)
self.assertTrue((default_install / "app/current").is_symlink())
self.assertEqual(
(default_install / "app/current").resolve().stat().st_mode & 0o777,
0o755,
)
launcher = home / ".local/bin/pristontale-eu"
desktop = data_home / "applications/eu.pristontale.PTLinux.desktop"
icon = data_home / "pixmaps/eu.pristontale.PTLinux.png"
self.assertTrue(launcher.is_file())
self.assertEqual(
icon.read_bytes(),
(PROJECT_ROOT / "packaging/eu.pristontale.PTLinux.png").read_bytes(),
)
entry = desktop.read_text(encoding="utf-8")
exec_line = next(line for line in entry.splitlines() if line.startswith("Exec="))
icon_line = next(line for line in entry.splitlines() if line.startswith("Icon="))
self.assertEqual(
exec_line,
f"Exec={quote_exec_argument(str(launcher))} gui",
)
self.assertEqual(icon_line, f"Icon={escape_icon_path(str(icon))}")
self.assertIn("StartupWMClass=PristonTaleEU", entry)
self.assertNotIn("TryExec=", entry)
self.assertIn(
f"install_directory={quote_shell_word(str(default_install))}",
launcher.read_text(encoding="utf-8"),
)
self.assertIn(
'exec "$python_bin" -B -P -m ptlinux',
launcher.read_text(encoding="utf-8"),
)
arguments = shlex.split(exec_line.removeprefix("Exec="))
self.assertEqual(arguments, [str(launcher), "gui"])
arguments[1] = "--help"
launched = subprocess.run(
arguments,
cwd=root,
env=environment,
capture_output=True,
text=True,
timeout=10,
check=False,
)
self.assertEqual(launched.returncode, 0, launched.stderr)
self.assertIn("PristonTale EU all-in-one", launched.stdout)
stale_directory = root / "unpacked-old-installer"
stale_package = stale_directory / "ptlinux"
stale_package.mkdir(parents=True)
(stale_package / "__init__.py").write_text("", encoding="utf-8")
(stale_package / "__main__.py").write_text(
'print("STALE LAUNCHER WAS IMPORTED")\n',
encoding="utf-8",
)
isolated = subprocess.run(
[launcher, "--version"],
cwd=stale_directory,
env=environment,
capture_output=True,
text=True,
timeout=10,
check=False,
)
self.assertEqual(isolated.returncode, 0, isolated.stderr)
self.assertEqual(isolated.stdout.strip(), f"pristontale-eu {__version__}")
self.assertNotIn("STALE LAUNCHER", isolated.stdout)
def test_installer_persists_custom_install_directory(self) -> None:
with tempfile.TemporaryDirectory() as temporary_directory:
root = Path(temporary_directory)
home = root / "home"
data_home = root / "desktop data"
install_directory = root / "External $(touch INJECTED)'s Game"
home.mkdir()
environment = os.environ.copy()
environment.pop("PTLINUX_INSTALL_DIR", None)
environment.update(
{
"HOME": str(home),
"XDG_DATA_HOME": str(data_home),
"PATH": "/usr/bin:/bin",
"PTLINUX_PYTHON": sys.executable,
}
)
result = subprocess.run(
[
"bash",
str(PROJECT_ROOT / "install.sh"),
"--install-dir",
str(install_directory),
"--skip-deps",
"--skip-doctor",
"--skip-runtime-bootstrap",
],
cwd=root,
env=environment,
capture_output=True,
text=True,
timeout=30,
check=False,
)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertTrue((install_directory / "app/current/ptlinux").is_dir())
self.assertTrue((install_directory / "app/current").is_symlink())
launcher = home / ".local/bin/pristontale-eu"
wrapper = launcher.read_text(encoding="utf-8")
self.assertIn(
f"install_directory={quote_shell_word(str(install_directory))}",
wrapper,
)
runtime_environment = environment.copy()
runtime_environment.pop("PTLINUX_INSTALL_DIR", None)
runtime_environment["XDG_DATA_HOME"] = str(root / "different data home")
launched = subprocess.run(
[str(launcher), "--help"],
cwd=root,
env=runtime_environment,
capture_output=True,
text=True,
timeout=10,
check=False,
)
self.assertEqual(launched.returncode, 0, launched.stderr)
self.assertIn("PristonTale EU all-in-one", launched.stdout)
self.assertFalse((root / "INJECTED").exists())
def test_installer_preserves_the_previous_default_on_upgrade(self) -> None:
with tempfile.TemporaryDirectory() as temporary_directory:
root = Path(temporary_directory)
home = root / "home"
data_home = root / "data"
legacy_install = data_home / "pristontale-eu"
legacy_game = (
legacy_install
/ "launcher-prefix/drive_c/Program Files (x86)/Pristontale EU/Game.exe"
)
legacy_game.parent.mkdir(parents=True)
legacy_game.write_bytes(b"existing managed game")
legacy_package = legacy_install / "app/ptlinux"
legacy_package.mkdir(parents=True)
(legacy_package / "__init__.py").write_text(
'__version__ = "0.4.7"\n',
encoding="utf-8",
)
settings_file = home / ".config/pristontale-eu/settings.json"
settings_file.parent.mkdir(parents=True)
settings_file.write_text(
json.dumps(
{
"borderless": False,
"runtime": "proton7",
"proton_sync": "esync",
}
),
encoding="utf-8",
)
environment = os.environ.copy()
environment.pop("PTLINUX_INSTALL_DIR", None)
environment.update(
{
"HOME": str(home),
"XDG_DATA_HOME": str(data_home),
"PATH": "/usr/bin:/bin",
"PTLINUX_PYTHON": sys.executable,
}
)
result = subprocess.run(
[
"bash",
str(PROJECT_ROOT / "install.sh"),
"--skip-deps",
"--skip-doctor",
"--skip-runtime-bootstrap",
],
cwd=PROJECT_ROOT,
env=environment,
capture_output=True,
text=True,
timeout=30,
check=False,
)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("preserving its previous data location", result.stdout)
launcher = home / ".local/bin/pristontale-eu"
self.assertIn(
f"install_directory={quote_shell_word(str(legacy_install))}",
launcher.read_text(encoding="utf-8"),
)
self.assertFalse((home / "Games/PristontaleEU/app").exists())
self.assertTrue((legacy_install / "app/current").is_symlink())
settings_result = subprocess.run(
[launcher, "--json", "settings", "--init"],
cwd=root,
env=environment,
capture_output=True,
text=True,
timeout=10,
check=False,
)
self.assertEqual(settings_result.returncode, 0, settings_result.stderr)
migrated = json.loads(settings_result.stdout)["settings"]
self.assertFalse(migrated["borderless"])
self.assertEqual(migrated["runtime"], "ge-proton")
self.assertEqual(migrated["proton_sync"], "esync")
persisted = json.loads(settings_file.read_text(encoding="utf-8"))
self.assertEqual(persisted["runtime"], "ge-proton")
self.assertIs(persisted["ge_proton_default_migrated"], True)
def test_production_release_requires_a_signing_key(self) -> None:
with tempfile.TemporaryDirectory() as temporary_directory:
output = Path(temporary_directory) / "output"
environment = os.environ.copy()
environment.pop("PTLINUX_SIGNING_KEY", None)
environment["PTLINUX_RELEASE_MODE"] = "production"
environment["PTLINUX_OUTPUT_DIRECTORY"] = str(output)
result = subprocess.run(
["bash", str(PROJECT_ROOT / "tools/build-aio-release.sh")],
cwd=PROJECT_ROOT,
env=environment,
capture_output=True,
text=True,
timeout=10,
check=False,
)
self.assertEqual(result.returncode, 2)
self.assertIn("PTLINUX_SIGNING_KEY is required", result.stderr)
self.assertFalse(output.exists())
def test_production_release_rejects_a_key_path_inside_the_checkout(self) -> None:
with tempfile.TemporaryDirectory() as temporary_directory:
output = Path(temporary_directory) / "output"
environment = os.environ.copy()
environment["PTLINUX_RELEASE_MODE"] = "production"
environment["PTLINUX_OUTPUT_DIRECTORY"] = str(output)
environment["PTLINUX_PYTHON"] = sys.executable
environment["PTLINUX_SIGNING_KEY"] = str(
PROJECT_ROOT / "release-keys/fixture-private.pem"
)
result = subprocess.run(
["bash", str(PROJECT_ROOT / "tools/build-aio-release.sh")],
cwd=PROJECT_ROOT,
env=environment,
capture_output=True,
text=True,
timeout=10,
check=False,
)
self.assertEqual(result.returncode, 2)
self.assertIn("outside the repository", result.stderr)
self.assertFalse(output.exists())
def test_release_artifact_signature_is_generated_and_verified(self) -> None:
openssl = shutil.which("openssl")
if openssl is None:
self.skipTest("OpenSSL is unavailable")
with tempfile.TemporaryDirectory() as temporary_directory:
root = Path(temporary_directory)
private_key = root / "private.pem"
public_key = root / "public.pem"
artifact = root / "installer.tar.gz"
artifact.write_bytes(b"release bytes")
commands = (
[
openssl,
"genpkey",
"-quiet",
"-algorithm",
"RSA",
"-pkeyopt",
"rsa_keygen_bits:2048",
"-out",
str(private_key),
],
[
openssl,
"pkey",
"-in",
str(private_key),
"-pubout",
"-out",
str(public_key),
],
)
for command in commands:
generated = subprocess.run(
command,
capture_output=True,
text=True,
timeout=20,
check=False,
)
self.assertEqual(generated.returncode, 0, generated.stderr)
private_key.chmod(0o600)
signature = sign_release_artifact(
artifact,
signing_key=private_key,
public_key=public_key,
)
self.assertTrue(signature.is_file())
self.assertEqual(signature.stat().st_mode & 0o777, 0o644)
artifact.write_bytes(b"tampered release bytes")
verification = subprocess.run(
[
openssl,
"dgst",
"-sha256",
"-verify",
str(public_key),
"-signature",
str(signature),
str(artifact),
],
capture_output=True,
text=True,
timeout=20,
check=False,
)
self.assertNotEqual(verification.returncode, 0)
def test_release_builder_packages_current_updater_sources(self) -> None:
self.assertIn(
f'version = "{__version__}"',
(PROJECT_ROOT / "pyproject.toml").read_text(encoding="utf-8"),
)
with tempfile.TemporaryDirectory() as temporary_directory:
output = Path(temporary_directory)
environment = os.environ.copy()
environment.pop("PTLINUX_VERSION", None)
environment["PTLINUX_OUTPUT_DIRECTORY"] = str(output)
environment["SOURCE_DATE_EPOCH"] = "0"
result = subprocess.run(
["bash", str(PROJECT_ROOT / "tools/build-aio-release.sh")],
cwd=PROJECT_ROOT,
env=environment,
capture_output=True,
text=True,
timeout=30,
check=False,
)
self.assertEqual(result.returncode, 0, result.stderr)
release_name = (
f"pristontale-eu-linux-installer-v{__version__.replace('.', '-')}"
)
archive = output / f"{release_name}.tar.gz"
checksum = archive.with_suffix(archive.suffix + ".sha256")
self.assertTrue(archive.is_file())
self.assertTrue(checksum.is_file())
self.assertFalse(Path(f"{archive}.sig").exists())
self.assertTrue((output / "launcher-update-public.pem").is_file())
zip_archive = output / f"{release_name}.zip"
self.assertTrue(zip_archive.is_file())
self.assertFalse(Path(f"{zip_archive}.sig").exists())
update_archive = output / f"pristontale-eu-linux-update-v{__version__}.tar.gz"
self.assertTrue(update_archive.is_file())
update_checksum = update_archive.with_suffix(update_archive.suffix + ".sha256")
self.assertTrue(update_checksum.is_file())
manifest = output / "stable.json"
self.assertTrue(manifest.is_file())
for artifact in (
archive,
checksum,
update_archive,
update_checksum,
manifest,
):
self.assertEqual(artifact.stat().st_mode & 0o777, 0o644)
expected_sha256 = checksum.read_text(encoding="utf-8").split()[0]
self.assertEqual(
hashlib.sha256(archive.read_bytes()).hexdigest(),
expected_sha256,
)
release_root = release_name
with tarfile.open(archive, "r:gz") as release:
members = release.getmembers()
names = {member.name for member in members}
self.assertTrue(
all(member.mode & 0o022 == 0 for member in members),
"release entries must not be group- or world-writable",
)
self.assertEqual(
next(member.mode for member in members if member.name == release_root),
0o755,
)
self.assertEqual(
{name for name in names if name.endswith(".md")},
{f"{release_root}/README.md"},
)
self.assertIn(f"{release_root}/ptlinux/desktop_entry.py", names)
self.assertIn(f"{release_root}/ptlinux/install_status.py", names)
self.assertIn(f"{release_root}/ptlinux/ui/dashboard.py", names)
self.assertIn(
f"{release_root}/ptlinux/ui/components/progress_panel.py",
names,
)
self.assertIn(f"{release_root}/ptlinux/launcher_wrapper.py", names)
self.assertIn(f"{release_root}/ptlinux/self_update.py", names)
self.assertIn(f"{release_root}/ptlinux/container_host.py", names)
self.assertIn(f"{release_root}/release/config.json", names)
self.assertIn(f"{release_root}/scripts/__init__.py", names)
self.assertIn(f"{release_root}/scripts/repository_paths.py", names)
self.assertIn(f"{release_root}/scripts/release_lib.py", names)
self.assertFalse(any(name.startswith(f"{release_root}/devtools/") for name in names))
self.assertIn(
f"{release_root}/ptlinux/launcher-update-public.pem",
names,
)
self.assertIn(
f"{release_root}/ptlinux/native/pt-identity-probe.exe",
names,
)
self.assertIn(
f"{release_root}/ptlinux/native/ptr-bcrypt-proxy.dll",
names,
)
gamescope_name = (
f"{release_root}/ptlinux/native/"
"gamescope-3.16.20-ubuntu26.04-amd64"
)
self.assertIn(gamescope_name, names)
self.assertEqual(
next(member.mode for member in members if member.name == gamescope_name),
0o755,
)
for name in (
"gamescope-compat.README",
"gamescope-compat.copyright",
"gamescope-lazy-output-images.patch",
"gamescope-wayland-live-resize.patch",
"gamescope-wayland-letterbox-backing.patch",
"gamescope-wayland-libdecor-input-scope.patch",
"gamescope-keep-nested-windows-visible.patch",
):
self.assertIn(f"{release_root}/ptlinux/native/{name}", names)
self.assertNotIn(
f"{release_root}/ptlinux/native/pt-world-fix.exe",
names,
)
self.assertNotIn(f"{release_root}/ptlinux/world_fix.py", names)
archived_readme = release.extractfile(f"{release_root}/README.md")
self.assertIsNotNone(archived_readme)
assert archived_readme is not None
readme = archived_readme.read().decode("utf-8")
self.assertIn(release_name, readme)
self.assertIn("./install.sh", readme)
self.assertIn("pristontale-eu update --repair", readme)
self.assertIn("pristontale-eu --help", readme)
for command in (
"gui",
"doctor",
"container-host",
"fetch",
"setup",
"update",
"sync",
"wine-update",
"import-game",
"cache-compatibility",
"prepare",
"play",
"settings",
"launcher-update",
):
self.assertIn(f"pristontale-eu {command}", readme)
archived_depot = release.extractfile(f"{release_root}/ptlinux/depot.py")
self.assertIsNotNone(archived_depot)
assert archived_depot is not None
self.assertEqual(
archived_depot.read(),
(SOURCE_ROOT / "ptlinux/depot.py").read_bytes(),
)
archived_login_trace = release.extractfile(
f"{release_root}/ptlinux/login_trace.py"
)
self.assertIsNotNone(archived_login_trace)
assert archived_login_trace is not None
login_trace_source = archived_login_trace.read().decode("utf-8")
self.assertIn("LOGIN_TRACE_ENABLED = False", login_trace_source)
self.assertNotIn("+relay", login_trace_source)
self.assertNotIn("RelayInclude", login_trace_source)
with tarfile.open(update_archive, "r:gz") as update:
update_root = f"pristontale-eu-linux-update-v{__version__}"
update_members = update.getmembers()
update_names = {member.name for member in update_members}
self.assertIn(f"{update_root}/release.json", update_names)
self.assertIn(f"{update_root}/ptlinux/self_update.py", update_names)
self.assertIn(f"{update_root}/ptlinux/container_host.py", update_names)
self.assertIn(f"{update_root}/ptlinux/import_state.py", update_names)
self.assertIn(f"{update_root}/ptlinux/ui/dashboard.py", update_names)
self.assertIn(
f"{update_root}/ptlinux/ui/dialogs/settings_dialog.py",
update_names,
)
self.assertNotIn(
f"{update_root}/ptlinux/desktop_entry.py",
update_names,
)
self.assertNotIn(
f"{update_root}/ptlinux/launcher_wrapper.py",
update_names,
)
self.assertIn(
f"{update_root}/ptlinux/launcher-update-public.pem",
update_names,
)
self.assertIn(
f"{update_root}/ptlinux/native/pt-identity-probe.exe",
update_names,
)
self.assertIn(
f"{update_root}/ptlinux/native/ptr-bcrypt-proxy.dll",
update_names,
)
gamescope_name = (
f"{update_root}/ptlinux/native/"
"gamescope-3.16.20-ubuntu26.04-amd64"
)
self.assertIn(gamescope_name, update_names)
self.assertEqual(
next(
member.mode
for member in update_members
if member.name == gamescope_name
),
0o755,
)
for name in (
"gamescope-compat.README",
"gamescope-compat.copyright",
"gamescope-lazy-output-images.patch",
"gamescope-wayland-live-resize.patch",
"gamescope-wayland-letterbox-backing.patch",
"gamescope-wayland-libdecor-input-scope.patch",
"gamescope-keep-nested-windows-visible.patch",
):
self.assertIn(
f"{update_root}/ptlinux/native/{name}",
update_names,
)
self.assertNotIn(
f"{update_root}/ptlinux/native/pt-world-fix.exe",
update_names,
)
self.assertNotIn(f"{update_root}/ptlinux/world_fix.py", update_names)
def test_release_builder_rejects_unsafe_version_text(self) -> None:
with tempfile.TemporaryDirectory() as temporary_directory:
output = Path(temporary_directory) / "output"
environment = os.environ.copy()
environment["PTLINUX_VERSION"] = "../unsafe"
environment["PTLINUX_OUTPUT_DIRECTORY"] = str(output)
result = subprocess.run(
["bash", str(PROJECT_ROOT / "tools/build-aio-release.sh")],
cwd=PROJECT_ROOT,
env=environment,
capture_output=True,
text=True,
timeout=10,
check=False,
)
self.assertEqual(result.returncode, 2)
self.assertIn("expected X.Y.Z", result.stderr)
self.assertFalse(output.exists())
def test_compact_release_can_import_overlay_in_clean_interpreter(self) -> None:
with tempfile.TemporaryDirectory() as temporary_directory:
release_root = Path(temporary_directory)
shutil.copytree(SOURCE_ROOT / "ptlinux", release_root / "ptlinux")
(release_root / "ptdiag").mkdir()
shutil.copy2(
SOURCE_ROOT / "ptdiag" / "__init__.py",
release_root / "ptdiag" / "__init__.py",
)
shutil.copy2(
SOURCE_ROOT / "ptdiag" / "assbin.py",
release_root / "ptdiag" / "assbin.py",
)
shutil.copy2(
SOURCE_ROOT / "ptdiag" / "logs.py",
release_root / "ptdiag" / "logs.py",
)
environment = os.environ.copy()
environment["PYTHONPATH"] = str(release_root)
result = subprocess.run(
[sys.executable, "-c", "import ptlinux.overlay"],
cwd=release_root,
env=environment,
capture_output=True,
text=True,
timeout=10,
check=False,
)
self.assertEqual(result.returncode, 0, result.stderr)
if __name__ == "__main__":
unittest.main()