-
Notifications
You must be signed in to change notification settings - Fork 18
fix: portable runtime layout #111
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
71b2dba
b1181d7
558702b
ff74aba
5b766ca
28ce722
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ | |
| import tempfile | ||
| import unittest | ||
| from pathlib import Path | ||
| from unittest import mock | ||
|
|
||
| from scripts.ci import package_windows_portable as MODULE | ||
|
|
||
|
|
@@ -14,17 +15,30 @@ def make_project_layout( | |
| product_name: str = "AstrBot", | ||
| cargo_toml: str = '[package]\nname = "astrbot-desktop-tauri"\n', | ||
| marker_name: str = "portable.flag\n", | ||
| tauri_resources: dict[str, str] | None = None, | ||
| ) -> dict[str, Path]: | ||
| project_root = Path(self.enterContext(tempfile.TemporaryDirectory())) | ||
| script_path = project_root / "scripts" / "ci" / "package_windows_portable.py" | ||
| tauri_config_path = project_root / "src-tauri" / "tauri.conf.json" | ||
| cargo_toml_path = project_root / "src-tauri" / "Cargo.toml" | ||
| marker_path = project_root / MODULE.PORTABLE_RUNTIME_MARKER_RELATIVE_PATH | ||
| if tauri_resources is None: | ||
| tauri_resources = { | ||
| "../resources/backend": "backend", | ||
| "../resources/webui": "webui", | ||
| } | ||
|
|
||
| script_path.parent.mkdir(parents=True) | ||
| script_path.write_text("# placeholder") | ||
| tauri_config_path.parent.mkdir(parents=True) | ||
| tauri_config_path.write_text(json.dumps({"productName": product_name})) | ||
| tauri_config_path.write_text( | ||
| json.dumps( | ||
| { | ||
| "productName": product_name, | ||
| "bundle": {"resources": tauri_resources}, | ||
| } | ||
| ) | ||
| ) | ||
| cargo_toml_path.write_text(cargo_toml) | ||
| marker_path.parent.mkdir(parents=True, exist_ok=True) | ||
| marker_path.write_text(marker_name) | ||
|
|
@@ -198,6 +212,54 @@ def test_load_project_config_from_returns_root_product_and_marker(self): | |
| self.assertEqual(project_config.product_name, "AstrBot") | ||
| self.assertEqual(project_config.binary_name, "astrbot-desktop-tauri") | ||
| self.assertEqual(project_config.portable_marker_name, "portable.flag") | ||
| self.assertEqual(project_config.backend_layout_relative_path, Path("backend")) | ||
| self.assertEqual(project_config.webui_layout_relative_path, Path("webui")) | ||
|
|
||
| def test_load_project_config_from_reads_portable_layout_aliases_from_tauri_resources( | ||
| self, | ||
| ): | ||
| layout = self.make_project_layout( | ||
| tauri_resources={ | ||
| "../resources/backend": "runtime/backend", | ||
| "../resources/webui": "runtime/webui", | ||
| } | ||
| ) | ||
|
|
||
| project_config = MODULE.load_project_config_from(layout["script_path"]) | ||
|
|
||
| self.assertEqual( | ||
| project_config.backend_layout_relative_path, Path("runtime/backend") | ||
| ) | ||
| self.assertEqual( | ||
| project_config.webui_layout_relative_path, Path("runtime/webui") | ||
| ) | ||
|
|
||
| def test_load_project_config_from_requires_exact_tauri_resource_source_keys(self): | ||
| layout = self.make_project_layout( | ||
| tauri_resources={ | ||
| "./../resources/backend": "runtime/backend", | ||
| "../resources/webui": "runtime/webui", | ||
| } | ||
| ) | ||
|
|
||
| with self.assertRaisesRegex( | ||
| ValueError, | ||
| re.escape("Missing bundle.resources alias for ../resources/backend"), | ||
| ): | ||
| MODULE.load_project_config_from(layout["script_path"]) | ||
|
|
||
| def test_load_project_config_from_normalizes_windows_relpath_separators(self): | ||
| layout = self.make_project_layout() | ||
|
|
||
| with mock.patch.object( | ||
| MODULE.os.path, | ||
| "relpath", | ||
| side_effect=[r"..\resources\backend", r"..\resources\webui"], | ||
| ): | ||
| project_config = MODULE.load_project_config_from(layout["script_path"]) | ||
|
|
||
| self.assertEqual(project_config.backend_layout_relative_path, Path("backend")) | ||
| self.assertEqual(project_config.webui_layout_relative_path, Path("webui")) | ||
|
|
||
| def test_normalize_legacy_nightly_version_returns_base_version_and_suffix(self): | ||
| self.assertEqual( | ||
|
|
@@ -360,6 +422,8 @@ def test_resolve_main_executable_path_uses_binary_name_not_product_name(self): | |
| product_name="AstrBot", | ||
| binary_name="astrbot-desktop-tauri", | ||
| portable_marker_name="portable.flag", | ||
| backend_layout_relative_path=Path("backend"), | ||
| webui_layout_relative_path=Path("webui"), | ||
| ) | ||
|
|
||
| self.assertEqual( | ||
|
|
@@ -424,12 +488,20 @@ def test_populate_portable_root_copies_release_bundle_contents(self): | |
| self.assertTrue((destination_root / "WebView2Loader.dll").is_file()) | ||
| self.assertTrue( | ||
| ( | ||
| destination_root / "resources" / "backend" / "runtime-manifest.json" | ||
| destination_root | ||
| / project_config.backend_layout_relative_path | ||
| / "runtime-manifest.json" | ||
| ).is_file() | ||
| ) | ||
| self.assertTrue( | ||
| (destination_root / "resources" / "webui" / "index.html").is_file() | ||
| ( | ||
| destination_root | ||
| / project_config.webui_layout_relative_path | ||
| / "index.html" | ||
| ).is_file() | ||
| ) | ||
| self.assertFalse((destination_root / "resources" / "backend").exists()) | ||
| self.assertFalse((destination_root / "resources" / "webui").exists()) | ||
| self.assertTrue((destination_root / "kill-backend-processes.ps1").is_file()) | ||
| self.assertTrue((destination_root / "portable.flag").is_file()) | ||
| self.assertTrue((destination_root / MODULE.PORTABLE_README_NAME).is_file()) | ||
|
|
@@ -466,6 +538,8 @@ def test_add_portable_runtime_files_writes_marker_and_readme(self): | |
| product_name="AstrBot", | ||
| binary_name="astrbot-desktop-tauri", | ||
| portable_marker_name="portable.flag", | ||
| backend_layout_relative_path=Path("backend"), | ||
| webui_layout_relative_path=Path("webui"), | ||
| ) | ||
|
|
||
| MODULE.add_portable_runtime_files(root, project_config) | ||
|
|
@@ -477,34 +551,78 @@ def test_add_portable_runtime_files_writes_marker_and_readme(self): | |
| ) | ||
|
|
||
| def test_validate_portable_root_accepts_expected_layout(self): | ||
| layout = self.make_project_layout() | ||
| project_config = MODULE.load_project_config_from(layout["script_path"]) | ||
|
|
||
| with tempfile.TemporaryDirectory() as tmpdir: | ||
| root = Path(tmpdir) | ||
| (root / "AstrBot.exe").write_text("binary") | ||
| (root / "resources" / "backend").mkdir(parents=True) | ||
| (root / "resources" / "webui").mkdir(parents=True) | ||
| (root / "resources" / "backend" / "runtime-manifest.json").write_text("{}") | ||
| (root / "resources" / "webui" / "index.html").write_text("<html></html>") | ||
| (root / project_config.backend_layout_relative_path).mkdir(parents=True) | ||
| (root / project_config.webui_layout_relative_path).mkdir(parents=True) | ||
| ( | ||
|
Comment on lines
553
to
+562
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (testing): Add a validation test where backend/webui aliases are nested (e.g. The current Suggested implementation: MODULE.add_portable_runtime_files(root, project_config)
def test_validate_portable_root_accepts_expected_layout(self):
layout = self.make_project_layout()
project_config = MODULE.load_project_config_from(layout["script_path"])
with tempfile.TemporaryDirectory() as tmpdir:
root = Path(tmpdir)
# default layout: backend/ and webui/ at the portable root
(root / "AstrBot.exe").write_text("binary")
(root / "portable.flag").write_text("marker")
(root / "backend").mkdir()
(root / "webui").mkdir()
MODULE.validate_portable_root(root, project_config)
def test_validate_portable_root_accepts_nested_alias_layout(self):
layout = self.make_project_layout(
tauri_resources={
"../resources/backend": "runtime/backend",
"../resources/webui": "runtime/webui",
}
)
project_config = MODULE.load_project_config_from(layout["script_path"])
with tempfile.TemporaryDirectory() as tmpdir:
root = Path(tmpdir)
# binary + portable marker at root
(root / "AstrBot.exe").write_text("binary")
(root / "portable.flag").write_text("marker")
# resources are placed under their aliased nested locations
(root / "runtime" / "backend").mkdir(parents=True)
(root / "runtime" / "webui").mkdir(parents=True)
# Intentionally do NOT create root-level backend/ or webui/ directories.
# validate_portable_root should honor the aliased paths instead.
MODULE.validate_portable_root(root, project_config)If |
||
| root / project_config.backend_layout_relative_path / "runtime-manifest.json" | ||
| ).write_text("{}") | ||
| (root / project_config.webui_layout_relative_path / "index.html").write_text( | ||
| "<html></html>" | ||
| ) | ||
|
|
||
| MODULE.validate_portable_root(root, project_config) | ||
|
|
||
| MODULE.validate_portable_root(root) | ||
| def test_validate_portable_root_accepts_nested_alias_layout(self): | ||
| layout = self.make_project_layout( | ||
| tauri_resources={ | ||
| "../resources/backend": "runtime/backend", | ||
| "../resources/webui": "runtime/webui", | ||
| } | ||
| ) | ||
| project_config = MODULE.load_project_config_from(layout["script_path"]) | ||
|
|
||
| with tempfile.TemporaryDirectory() as tmpdir: | ||
| root = Path(tmpdir) | ||
| (root / "AstrBot.exe").write_text("binary") | ||
| (root / project_config.portable_marker_name).write_text("marker") | ||
| (root / project_config.backend_layout_relative_path).mkdir(parents=True) | ||
| (root / project_config.webui_layout_relative_path).mkdir(parents=True) | ||
| ( | ||
| root / project_config.backend_layout_relative_path / "runtime-manifest.json" | ||
| ).write_text("{}") | ||
| (root / project_config.webui_layout_relative_path / "index.html").write_text( | ||
| "<html></html>" | ||
| ) | ||
|
|
||
| MODULE.validate_portable_root(root, project_config) | ||
|
|
||
| self.assertFalse((root / "backend").exists()) | ||
| self.assertFalse((root / "webui").exists()) | ||
|
|
||
| def test_validate_portable_root_requires_expected_files(self): | ||
| layout = self.make_project_layout() | ||
| project_config = MODULE.load_project_config_from(layout["script_path"]) | ||
|
|
||
| with tempfile.TemporaryDirectory() as tmpdir: | ||
| root = Path(tmpdir) | ||
| (root / "AstrBot.exe").write_text("binary") | ||
|
|
||
| with self.assertRaisesRegex(ValueError, "runtime-manifest.json"): | ||
| MODULE.validate_portable_root(root) | ||
| MODULE.validate_portable_root(root, project_config) | ||
|
|
||
| def test_validate_portable_root_requires_top_level_exe(self): | ||
| layout = self.make_project_layout() | ||
| project_config = MODULE.load_project_config_from(layout["script_path"]) | ||
|
|
||
| with tempfile.TemporaryDirectory() as tmpdir: | ||
| root = Path(tmpdir) | ||
| (root / "resources" / "backend").mkdir(parents=True) | ||
| (root / "resources" / "webui").mkdir(parents=True) | ||
| (root / "resources" / "backend" / "runtime-manifest.json").write_text("{}") | ||
| (root / "resources" / "webui" / "index.html").write_text("<html></html>") | ||
| (root / project_config.backend_layout_relative_path).mkdir(parents=True) | ||
| (root / project_config.webui_layout_relative_path).mkdir(parents=True) | ||
| ( | ||
| root / project_config.backend_layout_relative_path / "runtime-manifest.json" | ||
| ).write_text("{}") | ||
| (root / project_config.webui_layout_relative_path / "index.html").write_text( | ||
| "<html></html>" | ||
| ) | ||
|
|
||
| with self.assertRaisesRegex(ValueError, r"top-level \*\.exe"): | ||
| MODULE.validate_portable_root(root) | ||
| MODULE.validate_portable_root(root, project_config) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.