import stat
import tarfile
import tempfile
import unittest
import zipfile
from pathlib import Path
from scripts.package_release import (
ReleasePackageConfig,
_read_shader_preset_paths,
collect_shader_dependencies,
create_release_archive,
main,
)
def write_text(path: Path, text: str) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(text, encoding="utf-8")
def write_bytes(path: Path, data: bytes = b"test") -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(data)
def path_exists_with_exact_case(repo_root: Path, relative_path: Path) -> bool:
current = repo_root
for part in relative_path.parts:
if not current.is_dir():
return False
entries = {entry.name for entry in current.iterdir()}
if part not in entries:
return False
current = current / part
return current.exists()
class PackageReleaseTests(unittest.TestCase):
def test_repository_shader_dependencies_are_collectable(self) -> None:
repo_root = Path(__file__).resolve().parents[1]
dependencies = collect_shader_dependencies(repo_root)
self.assertIn(Path("shaders/stock.slangp"), dependencies)
def test_repository_shader_preset_paths_match_exact_vendor_casing(self) -> None:
repo_root = Path(__file__).resolve().parents[1]
preset_paths = _read_shader_preset_paths(repo_root / "src/platform/shaders.rs")
mismatches = [
path
for path in preset_paths
if not path_exists_with_exact_case(repo_root, path)
]
self.assertEqual(mismatches, [])
def test_collect_shader_dependencies_resolves_configured_preset_graph(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir_str:
repo_root = Path(temp_dir_str)
write_text(
repo_root / "src/platform/shaders.rs",
"""
pub const SHADER_PRESETS: &[(&str, &str)] = &[
(
"crt",
"vendor/slang-shaders/crt/preset.slangp",
),
];
""",
)
write_text(
repo_root / "vendor/slang-shaders/crt/preset.slangp",
'#reference "../common/base.slangp"\n'
"shader0 = pass.slang\n"
'textures = "lut.png"\n',
)
write_text(
repo_root / "vendor/slang-shaders/crt/pass.slang",
'#include "shared.inc"\n',
)
write_text(
repo_root / "vendor/slang-shaders/crt/shared.inc", "#define TEST 1\n"
)
write_bytes(repo_root / "vendor/slang-shaders/crt/lut.png")
write_text(
repo_root / "vendor/slang-shaders/common/base.slangp",
"shader0 = base.slang\n",
)
write_text(
repo_root / "vendor/slang-shaders/common/base.slang", "void main() {}\n"
)
dependencies = collect_shader_dependencies(repo_root)
self.assertEqual(
dependencies,
{
Path("vendor/slang-shaders/crt/preset.slangp"),
Path("vendor/slang-shaders/crt/pass.slang"),
Path("vendor/slang-shaders/crt/shared.inc"),
Path("vendor/slang-shaders/crt/lut.png"),
Path("vendor/slang-shaders/common/base.slangp"),
Path("vendor/slang-shaders/common/base.slang"),
},
)
def test_create_tar_gz_archive_uses_release_layout_and_excludes_scripts(
self,
) -> None:
with tempfile.TemporaryDirectory() as temp_dir_str:
repo_root = Path(temp_dir_str)
output_dir = repo_root / "dist"
binary_path = repo_root / "target/release/neser"
write_bytes(binary_path, b"#!/bin/sh\n")
binary_path.chmod(0o755)
write_bytes(repo_root / "assets/fonts/NunitoSans-Regular.ttf")
write_text(repo_root / "gamecontrollerdb.txt", "controller mappings\n")
write_text(repo_root / "neser.conf.example", "# config\n")
write_text(repo_root / "README.md", "# neser\n")
write_text(repo_root / "README-NES.md", "# nes\n")
write_text(repo_root / "README-GB.md", "# gb\n")
write_text(repo_root / "README-GBA.md", "# gba\n")
write_text(repo_root / "LICENSE", "license\n")
write_text(repo_root / "shaders/stock.slangp", "shader0 = stock.slang\n")
write_text(repo_root / "shaders/stock.slang", "void main() {}\n")
write_text(
repo_root / "src/platform/shaders.rs",
'pub const SHADER_PRESETS: &[(&str, &str)] = &[("none", '
'"shaders/stock.slangp")];',
)
write_text(repo_root / "scripts/dev-only.py", "print('dev only')\n")
archive_path = create_release_archive(
ReleasePackageConfig(
repo_root=repo_root,
binary_path=binary_path,
output_dir=output_dir,
target="x86_64-unknown-linux-gnu",
archive_format="tar.gz",
)
)
self.assertEqual(archive_path.name, "neser-x86_64-unknown-linux-gnu.tar.gz")
with tarfile.open(archive_path, "r:gz") as archive:
names = set(archive.getnames())
self.assertIn("neser/neser", names)
self.assertIn("neser/assets/fonts/NunitoSans-Regular.ttf", names)
self.assertIn("neser/gamecontrollerdb.txt", names)
self.assertIn("neser/neser.conf.example", names)
self.assertIn("neser/README.md", names)
self.assertIn("neser/README-NES.md", names)
self.assertIn("neser/README-GB.md", names)
self.assertIn("neser/README-GBA.md", names)
self.assertIn("neser/LICENSE", names)
self.assertIn("neser/shaders/stock.slangp", names)
self.assertIn("neser/shaders/stock.slang", names)
self.assertFalse(
any(name.startswith("neser/scripts/") for name in names)
)
binary_info = archive.getmember("neser/neser")
self.assertTrue(binary_info.mode & stat.S_IXUSR)
def test_create_zip_archive_uses_windows_release_layout(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir_str:
repo_root = Path(temp_dir_str)
output_dir = repo_root / "dist"
binary_path = repo_root / "target/release/neser.exe"
write_bytes(binary_path, b"MZ")
write_bytes(repo_root / "assets/fonts/NunitoSans-Regular.ttf")
write_text(repo_root / "gamecontrollerdb.txt", "controller mappings\n")
write_text(repo_root / "neser.conf.example", "# config\n")
write_text(repo_root / "README.md", "# neser\n")
write_text(repo_root / "README-NES.md", "# nes\n")
write_text(repo_root / "README-GB.md", "# gb\n")
write_text(repo_root / "README-GBA.md", "# gba\n")
write_text(repo_root / "LICENSE", "license\n")
write_text(repo_root / "shaders/stock.slangp", "shader0 = stock.slang\n")
write_text(repo_root / "shaders/stock.slang", "void main() {}\n")
write_text(
repo_root / "src/platform/shaders.rs",
'pub const SHADER_PRESETS: &[(&str, &str)] = &[("none", '
'"shaders/stock.slangp")];',
)
write_text(repo_root / "scripts/dev-only.py", "print('dev only')\n")
archive_path = create_release_archive(
ReleasePackageConfig(
repo_root=repo_root,
binary_path=binary_path,
output_dir=output_dir,
target="x86_64-pc-windows-msvc",
archive_format="zip",
binary_name="neser.exe",
)
)
self.assertEqual(archive_path.name, "neser-x86_64-pc-windows-msvc.zip")
with zipfile.ZipFile(archive_path) as archive:
names = set(archive.namelist())
self.assertIn("neser/neser.exe", names)
self.assertIn("neser/gamecontrollerdb.txt", names)
self.assertIn("neser/README-NES.md", names)
self.assertIn("neser/README-GB.md", names)
self.assertIn("neser/README-GBA.md", names)
self.assertIn("neser/shaders/stock.slangp", names)
self.assertFalse(any(name.startswith("neser/scripts/") for name in names))
def test_main_creates_archive_from_cli_arguments(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir_str:
repo_root = Path(temp_dir_str)
output_dir = repo_root / "dist"
binary_path = repo_root / "target/release/neser"
write_bytes(binary_path, b"#!/bin/sh\n")
binary_path.chmod(0o755)
write_bytes(repo_root / "assets/fonts/NunitoSans-Regular.ttf")
write_text(repo_root / "gamecontrollerdb.txt", "controller mappings\n")
write_text(repo_root / "neser.conf.example", "# config\n")
write_text(repo_root / "README.md", "# neser\n")
write_text(repo_root / "README-NES.md", "# nes\n")
write_text(repo_root / "README-GB.md", "# gb\n")
write_text(repo_root / "README-GBA.md", "# gba\n")
write_text(repo_root / "LICENSE", "license\n")
write_text(repo_root / "shaders/stock.slangp", "shader0 = stock.slang\n")
write_text(repo_root / "shaders/stock.slang", "void main() {}\n")
write_text(
repo_root / "src/platform/shaders.rs",
'pub const SHADER_PRESETS: &[(&str, &str)] = &[("none", '
'"shaders/stock.slangp")];',
)
exit_code = main(
[
"--repo-root",
str(repo_root),
"--binary",
str(binary_path),
"--output-dir",
str(output_dir),
"--target",
"x86_64-unknown-linux-gnu",
"--format",
"tar.gz",
]
)
self.assertEqual(exit_code, 0)
self.assertTrue(
(output_dir / "neser-x86_64-unknown-linux-gnu.tar.gz").exists()
)
if __name__ == "__main__":
unittest.main()