Track install state by installation id

This commit is contained in:
pleb
2026-07-09 20:04:10 -07:00
parent 3154d71988
commit 8bbca1a872
16 changed files with 382 additions and 111 deletions
+125 -12
View File
@@ -26,7 +26,14 @@ from plugin_helper.models import Dependency, Lockfile, LockedPlugin, Registry, R
from plugin_helper.operations import enable_disabled_plugin
from plugin_helper.planner import create_plan
from plugin_helper.scanner import scan_bootstrap_files, scan_instance
from plugin_helper.state import downloads_dir, load_installed_state, plugin_downloads_dir, save_bootstrap_state, save_installed_state
from plugin_helper.state import (
downloads_dir,
installation_id,
load_installed_state,
plugin_downloads_dir,
save_bootstrap_state,
save_installed_state,
)
from plugin_helper.tui import InstallationChoice, PluginHelperTui
from plugin_helper.update_runner import run_update
from plugin_helper.updates import check_updates
@@ -863,10 +870,112 @@ sha256 = "{sha256_file(asset)}"
self.assertEqual(status, 0)
self.assertEqual((instance / "Plugins" / "Example.dll").read_bytes(), b"managed dll")
updated = load_installed_state(state, "1.40.8")
install_id = installation_id(
profile_id=None,
root=instance_root,
instance="1.40.8",
instance_path=instance,
)
updated = load_installed_state(state, "1.40.8", install_id=install_id)
self.assertIn("example", updated["plugins"])
self.assertNotIn("example", updated["disabledPlugins"])
def test_install_states_are_separate_for_duplicate_instance_names(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
work = Path(tmp)
linux_root = work / "linux"
windows_root = work / "windows"
linux_instance = linux_root / "1.40.8"
windows_instance = windows_root / "1.40.8"
state = work / "state"
registry_dir = work / "registry"
locks_dir = work / "locks"
registry_dir.mkdir()
locks_dir.mkdir()
for instance in (linux_instance, windows_instance):
(instance / "Beat Saber_Data").mkdir(parents=True)
(instance / "Plugins").mkdir()
asset = plugin_downloads_dir(state, "1.40.8", "example") / "Example.dll"
asset.write_bytes(b"managed dll")
self.assertEqual(asset.parent, state / "cache" / "downloads" / "1.40.8" / "example")
(registry_dir / "plugins.toml").write_text(
"""
[[plugins]]
id = "example"
name = "Example"
repo = "owner/example"
asset_patterns = ["*.dll"]
install_strategy = "dll-to-plugins"
""".lstrip(),
encoding="utf-8",
)
(locks_dir / "1.40.8.lock.toml").write_text(
f"""
beat_saber_version = "1.40.8"
instance = "1.40.8"
[[plugins]]
id = "example"
repo = "owner/example"
tag = "v1.0.0"
asset = "Example.dll"
sha256 = "{sha256_file(asset)}"
""".lstrip(),
encoding="utf-8",
)
linux_id = installation_id(
profile_id="shared",
root=linux_root,
instance="1.40.8",
instance_path=linux_instance,
)
windows_id = installation_id(
profile_id="shared",
root=windows_root,
instance="1.40.8",
instance_path=windows_instance,
)
disabled_state = {
"instance": "1.40.8",
"plugins": {},
"disabledPlugins": {
"example": {
"installedAt": "2026-06-14T17:18:40Z",
"disabledAt": "2026-06-14T17:20:00Z",
"files": [
{
"path": "Plugins/Example.dll",
"sha256": sha256_file(asset),
"size": asset.stat().st_size,
}
],
}
},
}
save_installed_state(state, "1.40.8", json.loads(json.dumps(disabled_state)), install_id=linux_id)
save_installed_state(state, "1.40.8", json.loads(json.dumps(disabled_state)), install_id=windows_id)
with patch("plugin_helper.operations.repo_root", return_value=work):
result = enable_disabled_plugin(
instance="1.40.8",
instance_path=linux_instance,
state_root=state,
plugin_id="example",
repo=work,
install_id=linux_id,
)
self.assertTrue((linux_instance / "Plugins" / "Example.dll").is_file())
self.assertFalse((windows_instance / "Plugins" / "Example.dll").exists())
self.assertIn(f"installs/{linux_id}/plans", result["planPath"])
linux_state = load_installed_state(state, "1.40.8", install_id=linux_id)
windows_state = load_installed_state(state, "1.40.8", install_id=windows_id)
self.assertIn("example", linux_state["plugins"])
self.assertNotIn("example", linux_state["disabledPlugins"])
self.assertEqual(windows_state["plugins"], {})
self.assertIn("example", windows_state["disabledPlugins"])
def test_save_and_restore_known_good_set(self) -> None:
from plugin_helper.operations import restore_known_good_set, save_known_good_set
from plugin_helper.state import load_known_good_state, save_installed_state
@@ -1006,7 +1115,7 @@ instance = "1.40.8"
apply_plan(plan, state)
self.assertEqual((instance / "IPA" / "Pending" / "Plugins" / "Example.dll").read_bytes(), b"dll")
def test_plan_still_finds_legacy_flat_downloads(self) -> None:
def test_plan_finds_shared_version_downloads(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
work = Path(tmp)
instance = work / "instances" / "1.40.8"
@@ -1014,7 +1123,7 @@ instance = "1.40.8"
instance.mkdir(parents=True)
(instance / "Beat Saber_Data").mkdir()
asset = downloads_dir(state, "1.40.8") / "Example.dll"
asset.write_bytes(b"legacy flat download")
asset.write_bytes(b"shared version download")
plan, _ = create_plan(
instance="1.40.8",
@@ -2336,6 +2445,7 @@ sha256 = "{sha256_file(asset)}"
state,
"1.40.8",
{"instance": "1.40.8", "plugins": plugins, "disabledPlugins": disabled_plugins},
install_id="test",
)
choice = InstallationChoice(
install_id="test",
@@ -2405,6 +2515,7 @@ instance = "1.40.8"
state,
"1.40.8",
{"instance": "1.40.8", "plugins": plugins_state, "disabledPlugins": {}},
install_id="test",
)
choice = InstallationChoice(
install_id="test",
@@ -2440,6 +2551,8 @@ class PluginHelperTuiTests(unittest.IsolatedAsyncioTestCase):
table = app.query_one(DataTable)
self.assertEqual(table.row_count, 2)
self.assertEqual(app.mode, "installations")
self.assertEqual(str(table.get_cell_at(Coordinate(0, 3))), "/tmp/state-linux/installs/linux")
self.assertEqual(str(table.get_cell_at(Coordinate(1, 3))), "/tmp/state-windows/installs/windows")
async def test_single_instance_skips_installation_picker(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
@@ -2464,7 +2577,7 @@ class PluginHelperTuiTests(unittest.IsolatedAsyncioTestCase):
await pilot.pause()
self.assertFalse((instance / "Plugins" / "Example.dll").exists())
updated = load_installed_state(state, "1.40.8")
updated = load_installed_state(state, "1.40.8", install_id=app.choices[0].install_id)
self.assertNotIn("example", updated["plugins"])
self.assertIn("example", updated["disabledPlugins"])
@@ -2498,7 +2611,7 @@ class PluginHelperTuiTests(unittest.IsolatedAsyncioTestCase):
await pilot.pause()
self.assertEqual((instance / "Plugins" / "Example.dll").read_bytes(), b"managed dll")
updated = load_installed_state(state, "1.40.8")
updated = load_installed_state(state, "1.40.8", install_id=app.choices[0].install_id)
self.assertIn("example", updated["plugins"])
self.assertNotIn("example", updated["disabledPlugins"])
@@ -2513,7 +2626,7 @@ class PluginHelperTuiTests(unittest.IsolatedAsyncioTestCase):
await pilot.pause()
self.assertEqual((instance / "Plugins" / "Example.dll").read_bytes(), b"managed dll")
updated = load_installed_state(state, "1.40.8")
updated = load_installed_state(state, "1.40.8", install_id=app.choices[0].install_id)
self.assertIn("example", updated["plugins"])
self.assertNotIn("example", updated.get("disabledPlugins", {}))
@@ -2526,7 +2639,7 @@ class PluginHelperTuiTests(unittest.IsolatedAsyncioTestCase):
await pilot.pause()
self.assertFalse((instance / "Plugins" / "Example.dll").exists())
updated = load_installed_state(state, "1.40.8")
updated = load_installed_state(state, "1.40.8", install_id=app.choices[0].install_id)
self.assertNotIn("example", updated["plugins"])
self.assertIn("example", updated["disabledPlugins"])
self.assertIn("Disabled 1 plugins", app.status_message)
@@ -2540,7 +2653,7 @@ class PluginHelperTuiTests(unittest.IsolatedAsyncioTestCase):
await pilot.pause()
self.assertEqual((instance / "Plugins" / "Example.dll").read_bytes(), b"managed dll")
updated = load_installed_state(state, "1.40.8")
updated = load_installed_state(state, "1.40.8", install_id=app.choices[0].install_id)
self.assertIn("example", updated["plugins"])
self.assertNotIn("example", updated["disabledPlugins"])
self.assertIn("Enabled 1 plugins", app.status_message)
@@ -2555,7 +2668,7 @@ class PluginHelperTuiTests(unittest.IsolatedAsyncioTestCase):
from plugin_helper.state import load_known_good_state
known_good = load_known_good_state(state, "1.40.8")
known_good = load_known_good_state(state, "1.40.8", install_id=app.choices[0].install_id)
self.assertEqual(known_good["pluginIds"], ["alpha", "beta"])
self.assertIn("Saved known-good set: 2 enabled plugins", app.status_message)
@@ -2577,7 +2690,7 @@ class PluginHelperTuiTests(unittest.IsolatedAsyncioTestCase):
self.assertIn("Restored known-good set", app.status_message)
self.assertTrue((instance / "Plugins" / "Beta.dll").exists())
updated = load_installed_state(state, "1.40.8")
updated = load_installed_state(state, "1.40.8", install_id=app.choices[0].install_id)
self.assertEqual(set(updated["plugins"]), {"alpha", "beta"})
async def test_restore_known_good_without_saved_set_reports_status(self) -> None:
@@ -2599,7 +2712,7 @@ class PluginHelperTuiTests(unittest.IsolatedAsyncioTestCase):
await pilot.pause()
self.assertEqual((instance / "Plugins" / "Example.dll").read_bytes(), b"changed dll")
updated = load_installed_state(state, "1.40.8")
updated = load_installed_state(state, "1.40.8", install_id=app.choices[0].install_id)
self.assertIn("example", updated["plugins"])
self.assertNotIn("example", updated.get("disabledPlugins", {}))
self.assertIn("hash mismatch", app.status_message)