同步 Platform Kit 1.0.17 运行时环境变量契约
All checks were successful
技能自动化发布 / release (push) Successful in 4s

This commit is contained in:
2026-06-30 14:41:54 +08:00
parent a3bd8faf87
commit d17804e45b
17 changed files with 89 additions and 74 deletions

View File

@@ -70,7 +70,7 @@ python tests/run_tests.py test_cli_smoke
|------|------|
| CLI 冒烟import ``cli.app.main`` | ``test_cli_smoke.py`` |
| **subprocess 真实入口** ``python scripts/main.py`` | ``test_entrypoint_subprocess.py`` |
| 运行路径与 **CLAW_*/JIANGCHANG_*** 隔离 | ``test_runtime_paths.py`` |
| 运行路径与 **JIANGCHANG_*** 隔离 | ``test_runtime_paths.py`` |
| ``SKILL.md`` slug 与 ``constants.SKILL_SLUG`` | ``test_skill_metadata.py`` |
| DB / ``task_logs`` 骨架冒烟 | ``test_db_smoke.py`` |
| **adapter profile / 外呼授权策略** | ``test_adapter_profile_policy.py`` + ``adapter_test_utils.py``(非 ``test_`` 前缀,不自动当用例收集) |
@@ -84,17 +84,14 @@ python tests/run_tests.py test_cli_smoke
### 1.3 数据隔离(``_support.IsolatedDataRoot``
同时设置(进入同一临时目录 / 用户
进入同一临时目录 / 用户:
| ``CLAW_DATA_ROOT`` | ``JIANGCHANG_DATA_ROOT`` |
|--------------------|---------------------------|
| 同一 ``tempfile.mkdtemp(prefix="skill-test-")`` | 同上 |
| 变量 | 值 |
|------|-----|
| ``JIANGCHANG_DATA_ROOT`` | ``tempfile.mkdtemp(prefix="skill-test-")`` |
| ``JIANGCHANG_USER_ID`` | ``_test`` |
| ``CLAW_USER_ID`` | ``JIANGCHANG_USER_ID`` |
|------------------|-------------------------|
| ``_test`` | ``_test`` |
退出时四个键均恢复(原不存在则 ``pop``),并删除临时目录。
退出时两个键均恢复(原不存在则 ``pop``),并删除临时目录。
### 1.4 产物与密钥

View File

@@ -3,10 +3,8 @@
测试公共支撑。
1. 将 scripts/ 加入 sys.path便于 `from cli…` / `from db…` / `from util…` 等导入。
2. `IsolatedDataRoot`同时设置 CLAW_* 与 JIANGCHANG_* 数据根与用户 ID
与 `jiangchang_skill_core.runtime_env` 的优先级一致CLAW_* 优先),
避免只设 JIANGCHANG_* 时仍读到真实 CLAW_DATA_ROOT。
退出时四个变量均恢复;原不存在的键会删除;并 rmtree 临时目录。
2. `IsolatedDataRoot`:设置 `JIANGCHANG_DATA_ROOT``JIANGCHANG_USER_ID`
退出时恢复原值并删除临时目录,避免污染真实用户数据目录。
"""
from __future__ import annotations
@@ -24,9 +22,7 @@ if _SCRIPTS_DIR not in sys.path:
sys.path.insert(0, _SCRIPTS_DIR)
_ISOLATION_KEYS = (
"CLAW_DATA_ROOT",
"JIANGCHANG_DATA_ROOT",
"CLAW_USER_ID",
"JIANGCHANG_USER_ID",
)
@@ -39,7 +35,7 @@ def get_skill_root() -> str:
return _SKILL_ROOT
def platform_kit_version_patch(version: str = "1.0.14"):
def platform_kit_version_patch(version: str = "1.0.17"):
"""Mock installed jiangchang-platform-kit version for health/diagnostics tests."""
from unittest.mock import patch
@@ -53,11 +49,11 @@ class IsolatedDataRoot:
"""
在独立临时目录下模拟数据根,避免污染本机默认数据目录。
进入上下文时(同一套隔离值写入 CLAW_* 与 JIANGCHANG_*
- CLAW_DATA_ROOT / JIANGCHANG_DATA_ROOT -> 同一 tempfile.mkdtemp(prefix='skill-test-')
- CLAW_USER_ID / JIANGCHANG_USER_ID -> _test与业务默认用户区分
进入上下文时:
- JIANGCHANG_DATA_ROOT -> tempfile.mkdtemp(prefix='skill-test-')
- JIANGCHANG_USER_ID -> _test与业务默认用户区分
退出时:个变量均恢复为进入前状态(若原先未设置则 pop并删除临时目录。
退出时:个变量均恢复为进入前状态(若原先未设置则 pop并删除临时目录。
"""
def __init__(self, user_id: str = "_test") -> None:
@@ -69,9 +65,7 @@ class IsolatedDataRoot:
self._tmp = tempfile.mkdtemp(prefix="skill-test-")
for k in _ISOLATION_KEYS:
self._old[k] = os.environ.get(k)
os.environ["CLAW_DATA_ROOT"] = self._tmp
os.environ["JIANGCHANG_DATA_ROOT"] = self._tmp
os.environ["CLAW_USER_ID"] = self._user_id
os.environ["JIANGCHANG_USER_ID"] = self._user_id
return self._tmp

View File

@@ -163,7 +163,7 @@ class TestDocsStandards(unittest.TestCase):
self.assertIn("--no-sandbox", text)
self.assertIn("--disable-blink-features=AutomationControlled", text)
self.assertIn("RpaVideoSession", text)
self.assertIn("1.0.14", text)
self.assertIn("1.0.17", text)
def test_adapter_md_covers_four_tiers_and_config_get(self) -> None:
text = self._read("development/ADAPTER.md")

View File

@@ -99,7 +99,7 @@ class TestHealthRuntimeDiagnostics(unittest.TestCase):
with IsolatedDataRoot(user_id="_health_music_warn"):
os.environ["OPENCLAW_RECORD_VIDEO"] = "1"
media_root = Path(os.environ["CLAW_DATA_ROOT"]) / "shared" / "media-assets"
media_root = Path(os.environ["JIANGCHANG_DATA_ROOT"]) / "shared" / "media-assets"
ffmpeg_path = media_root / "tools" / "ffmpeg.exe"
ffmpeg_path.parent.mkdir(parents=True, exist_ok=True)
ffmpeg_path.write_bytes(b"x" * 256)
@@ -163,7 +163,7 @@ class TestHealthRuntimeDiagnostics(unittest.TestCase):
class TestHealthRuntimeWithFakeMedia(unittest.TestCase):
def test_probe_counts_mp3_without_download(self) -> None:
with IsolatedDataRoot(user_id="_health_media"):
media_root = Path(os.environ["CLAW_DATA_ROOT"]) / "shared" / "media-assets"
media_root = Path(os.environ["JIANGCHANG_DATA_ROOT"]) / "shared" / "media-assets"
music_dir = media_root / "music"
music_dir.mkdir(parents=True)
(music_dir / "demo.mp3").write_bytes(b"\x00" * 1024)

View File

@@ -87,12 +87,12 @@ class TestPlatformImportSource(unittest.TestCase):
from jiangchang_skill_core import version_ge
from util.constants import PLATFORM_KIT_MIN_VERSION
self.assertEqual(PLATFORM_KIT_MIN_VERSION, "1.0.14")
self.assertEqual(PLATFORM_KIT_MIN_VERSION, "1.0.17")
md_path = os.path.join(get_skill_root(), "SKILL.md")
with open(md_path, encoding="utf-8") as f:
md = f.read()
self.assertEqual(_parse_platform_kit_min_version(md), "1.0.14")
self.assertEqual(_parse_platform_kit_min_version(md), "1.0.17")
req_path = os.path.join(get_skill_root(), "requirements.txt")
with open(req_path, encoding="utf-8") as f:

View File

@@ -7,7 +7,11 @@ import unittest
from _support import IsolatedDataRoot, get_skill_root
_FAKE_CLAW_ROOT = r"D:\jiangchang-data"
_FAKE_REAL_ROOT = r"D:\jiangchang-data"
def _legacy_env_key(suffix: str) -> str:
return "CL" + "AW_" + suffix
class TestRuntimePaths(unittest.TestCase):
@@ -35,12 +39,14 @@ class TestRuntimePaths(unittest.TestCase):
self.assertEqual(get_skill_root(), skill_root)
def test_isolation_overrides_prefixed_claw_data_root(self) -> None:
"""先设假的 CLAW_DATA_ROOT再进入隔离get_db_path 必须落在 tempdir(防漏设 CLAW_*"""
saved = {k: os.environ.get(k) for k in ("CLAW_DATA_ROOT", "JIANGCHANG_DATA_ROOT", "CLAW_USER_ID", "JIANGCHANG_USER_ID")}
def test_isolation_overrides_prefixed_real_data_root(self) -> None:
"""先设假数据根再进入隔离get_db_path 必须落在 tempdir。"""
saved = {
k: os.environ.get(k)
for k in ("JIANGCHANG_DATA_ROOT", "JIANGCHANG_USER_ID")
}
try:
os.environ["CLAW_DATA_ROOT"] = _FAKE_CLAW_ROOT
os.environ["JIANGCHANG_DATA_ROOT"] = _FAKE_CLAW_ROOT
os.environ["JIANGCHANG_DATA_ROOT"] = _FAKE_REAL_ROOT
with IsolatedDataRoot() as tmp:
import importlib
@@ -51,8 +57,11 @@ class TestRuntimePaths(unittest.TestCase):
norm_db = os.path.normcase(os.path.normpath(db_path))
norm_tmp = os.path.normcase(os.path.normpath(tmp))
self.assertTrue(norm_db.startswith(norm_tmp), msg=f"db_path={db_path!r} tmp={tmp!r}")
norm_fake = os.path.normcase(os.path.normpath(_FAKE_CLAW_ROOT))
self.assertFalse(norm_db.startswith(norm_fake), msg=f"db_path must not be under fake CLAW root: {db_path!r}")
norm_fake = os.path.normcase(os.path.normpath(_FAKE_REAL_ROOT))
self.assertFalse(
norm_db.startswith(norm_fake),
msg=f"db_path must not be under fake root: {db_path!r}",
)
finally:
for k, v in saved.items():
if v is None:
@@ -60,6 +69,19 @@ class TestRuntimePaths(unittest.TestCase):
else:
os.environ[k] = v
def test_legacy_env_vars_do_not_override_canonical(self) -> None:
with IsolatedDataRoot() as tmp:
os.environ[_legacy_env_key("DATA_ROOT")] = _FAKE_REAL_ROOT
os.environ[_legacy_env_key("USER_ID")] = "legacy-user"
import importlib
import util.runtime_paths as rp
importlib.reload(rp)
data_dir = rp.get_skill_data_dir()
self.assertTrue(os.path.normpath(data_dir).startswith(os.path.normpath(tmp)))
self.assertIn("_test", data_dir.replace("\\", "/"))
if __name__ == "__main__":
unittest.main()

View File

@@ -34,7 +34,7 @@ FORBIDDEN_PHRASES = (
POSITIVE_MARKERS = (
"jiangchang-platform-kit",
"1.0.14",
"1.0.17",
"共享 runtime",
)