From 93d69547b77904c397294c43b35e21f6c8568acc Mon Sep 17 00:00:00 2001 From: huashen <2494946808@qq.com> Date: Mon, 24 Aug 2026 02:35:52 +0800 Subject: [PATCH 1/3] test(fitbit): require fixture runtime python --- tests/test_manager_integration.py | 32 +++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/tests/test_manager_integration.py b/tests/test_manager_integration.py index 7dba436..e275171 100644 --- a/tests/test_manager_integration.py +++ b/tests/test_manager_integration.py @@ -1,8 +1,8 @@ from __future__ import annotations import hashlib +import os import shutil -import sys from pathlib import Path import pytest @@ -26,7 +26,7 @@ def _tree_digest(root: Path) -> str: def _stage_plugin(tmp_path: Path) -> Path: - """复制可执行 artifact,并复用当前测试解释器的依赖环境。""" + """复制可执行 artifact,并挂载调用方明确选择的依赖环境。""" source = tmp_path / "plugins" / "fitbit" shutil.copytree( @@ -41,16 +41,36 @@ def _stage_plugin(tmp_path: Path) -> Path: "node_modules", ), ) - (source / ".venv").symlink_to( - Path(sys.executable).parent.parent, - target_is_directory=True, - ) + fixture_python = Path(os.environ["AKASHIC_PLUGIN_FIXTURE_PYTHON"]) + (source / ".venv").symlink_to(fixture_python.parent.parent, target_is_directory=True) content_source = Path(content_plugin.__file__).resolve().parent content_target = source.parent / "content" shutil.copytree(content_source, content_target) return source +def test_stage_plugin_uses_explicit_fixture_python( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + artifact_python = tmp_path / "artifact" / ".venv" / "bin" / "python" + monkeypatch.setenv("AKASHIC_PLUGIN_FIXTURE_PYTHON", str(artifact_python)) + + plugin_root = _stage_plugin(tmp_path / "stage") + + assert (plugin_root / ".venv").readlink() == artifact_python.parent.parent + + +def test_stage_plugin_requires_explicit_fixture_python( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.delenv("AKASHIC_PLUGIN_FIXTURE_PYTHON", raising=False) + + with pytest.raises(KeyError, match="AKASHIC_PLUGIN_FIXTURE_PYTHON"): + _stage_plugin(tmp_path) + + @pytest.mark.asyncio async def test_manager_rebuilds_fitbit_runtime_on_exact_formal_root( tmp_path: Path, From b81978474a630a13cb8d9bd6f2fa17afb28b2d11 Mon Sep 17 00:00:00 2001 From: huashen <2494946808@qq.com> Date: Mon, 24 Aug 2026 02:42:35 +0800 Subject: [PATCH 2/3] test(fitbit): bind fixture runtime before staging --- .github/workflows/plugin-api-v3.yml | 1 + tests/test_manager_integration.py | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/.github/workflows/plugin-api-v3.yml b/.github/workflows/plugin-api-v3.yml index 932c9bb..e535709 100644 --- a/.github/workflows/plugin-api-v3.yml +++ b/.github/workflows/plugin-api-v3.yml @@ -55,6 +55,7 @@ jobs: - name: Verify Fitbit v3 composition env: AKASHIC_AGENT_ROOT: .akashic-core + AKASHIC_PLUGIN_FIXTURE_PYTHON: ${{ github.workspace }}/.venv/bin/python PYTHONPATH: .akashic-core run: .venv/bin/python -m pytest -q tests/ - uses: actions/setup-node@v4 diff --git a/tests/test_manager_integration.py b/tests/test_manager_integration.py index e275171..e9ab705 100644 --- a/tests/test_manager_integration.py +++ b/tests/test_manager_integration.py @@ -28,6 +28,7 @@ def _tree_digest(root: Path) -> str: def _stage_plugin(tmp_path: Path) -> Path: """复制可执行 artifact,并挂载调用方明确选择的依赖环境。""" + fixture_python = Path(os.environ["AKASHIC_PLUGIN_FIXTURE_PYTHON"]) source = tmp_path / "plugins" / "fitbit" shutil.copytree( ROOT, @@ -41,7 +42,6 @@ def _stage_plugin(tmp_path: Path) -> Path: "node_modules", ), ) - fixture_python = Path(os.environ["AKASHIC_PLUGIN_FIXTURE_PYTHON"]) (source / ".venv").symlink_to(fixture_python.parent.parent, target_is_directory=True) content_source = Path(content_plugin.__file__).resolve().parent content_target = source.parent / "content" @@ -70,6 +70,22 @@ def test_stage_plugin_requires_explicit_fixture_python( with pytest.raises(KeyError, match="AKASHIC_PLUGIN_FIXTURE_PYTHON"): _stage_plugin(tmp_path) + assert not (tmp_path / "plugins").exists() + + +def test_ci_creates_and_exports_absolute_fixture_python_before_pytest() -> None: + workflow = (ROOT / ".github/workflows/plugin-api-v3.yml").read_text( + encoding="utf-8" + ) + + create_runtime = workflow.index("python -m venv .venv") + export_runtime = workflow.index( + "AKASHIC_PLUGIN_FIXTURE_PYTHON: ${{ github.workspace }}/.venv/bin/python" + ) + run_pytest = workflow.index("run: .venv/bin/python -m pytest -q tests/") + + assert create_runtime < export_runtime < run_pytest + @pytest.mark.asyncio async def test_manager_rebuilds_fitbit_runtime_on_exact_formal_root( From be6e109d56934f50f4a4537db7d41c8a9a3ae829 Mon Sep 17 00:00:00 2001 From: huashen <2494946808@qq.com> Date: Mon, 24 Aug 2026 20:46:58 +0800 Subject: [PATCH 3/3] fix: load Fitbit runtime as a package --- plugin.py | 6 +++--- src/content_adapter.py | 2 +- tests/conftest.py | 6 ++++++ tests/test_plugin.py | 4 ++-- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/plugin.py b/plugin.py index bd304b5..c422dd4 100644 --- a/plugin.py +++ b/plugin.py @@ -21,13 +21,13 @@ MobileUiNavigation, ServiceKey, ) -from src.content_adapter import ( +from .src.content_adapter import ( BoundContentSource, FitbitContentRuntime, FitbitMonitorClient, ) -from src.mobile_reader import mobile_ui_query -from src.sleep_context import FitbitAdapterStore, SleepContextAppender +from .src.mobile_reader import mobile_ui_query +from .src.sleep_context import FitbitAdapterStore, SleepContextAppender class ContentSourceServices(Protocol): diff --git a/src/content_adapter.py b/src/content_adapter.py index e5828b1..fdff540 100644 --- a/src/content_adapter.py +++ b/src/content_adapter.py @@ -12,7 +12,7 @@ from agent.control.timer import TimerHandle, TimerStatus from agent.plugin_composition import Context, HealthHandle, PluginTimers -from src.sleep_context import FitbitAdapterStore +from .sleep_context import FitbitAdapterStore class BoundContentSource(Protocol): diff --git a/tests/conftest.py b/tests/conftest.py index 3eef713..d13f25f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,6 +3,7 @@ import os import sys from pathlib import Path +from types import ModuleType repo_root = Path(__file__).resolve().parents[1] @@ -13,3 +14,8 @@ for path in (repo_root, agent_root): if str(path) not in sys.path: sys.path.insert(0, str(path)) + +package = ModuleType("fitbit_test_plugin") +package.__path__ = [str(repo_root)] +package.__package__ = "fitbit_test_plugin" +sys.modules["fitbit_test_plugin"] = package diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 724842e..11459aa 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -27,8 +27,8 @@ from agent.plugins.static_manifest import load_static_plugin_manifest from plugins.content import plugin as content_plugin -import plugin as plugin_module -from plugin import FitbitConfig +from fitbit_test_plugin import plugin as plugin_module # pyright: ignore[reportMissingImports] +from fitbit_test_plugin.plugin import FitbitConfig # pyright: ignore[reportMissingImports] from src import mobile_reader from src.mobile_reader import mobile_ui_query