diff --git a/.task/archive/fix-svc-test-port-governance-batch1.md b/.task/archive/fix-svc-test-port-governance-batch1.md new file mode 100644 index 00000000..0433a611 --- /dev/null +++ b/.task/archive/fix-svc-test-port-governance-batch1.md @@ -0,0 +1,98 @@ +# fix/svc-test-port-governance-batch1 任务卡 + +任务主记录:PR #702 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/702(merge cb36b59acc);状态:已完成(回填);owner:weichen + +- 权威仓:ssh://git@192.168.3.2:2222/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= 3d3474a731f9ef3ee1f41935e63bca3afb70db56 +- 工作区:/home/weichen/projects/Agent-System-Dev-wt-fix-svc-test-port-governance-batch1(worktree);分支:fix/svc-test-port-governance-batch1 +- 分支类型:standalone;root_target:main;immediate_target:main;accepting_requirements:false +- 需求分流:verdict=NEW;selected_initiative=无;requirement_sha256=1723e9e3e6bd50bedc041afba3533c35e8b663edb12472f29c067df860df799b;triage_receipt=/tmp/pa1-triage.json;receipt_sha256=71d236166f2a1de8cecd6eb93042ef5199a09e2c49318dbf8eb9d5b5582a99e5 + +- 授权与交付终点:<本次允许的动作与终点;未授权事项写明> +- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进) +- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md) +- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入) +- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位) + +## 目标与非目标 + +<预期结果、受影响入口/模块/契约;明确不做什么。> + +## Agent 实施收据 + +路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。 + +| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 | +|---|---|---|---|---|---|---|---|---|---|---| +| 待定义 | — | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | — | + +## 同步记录 + +| 节点 | sync_base_sha | tested_head_sha | checked_at | +|---|---|---|---| +| 启动 | 3d3474a731f9ef3ee1f41935e63bca3afb70db56 | 3d3474a731f9ef3ee1f41935e63bca3afb70db56 | 2026-10-22T11:15:53Z | + +## 证据(Evidence) + + +| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) | +|---|---|---|---|---|---| + +## 结果与验证 + +- 结果: +- 验证:<结论摘要 PASS/FAIL/SKIP/NOT_RUN 逐项;逐条证据由 make evidence 写入上方证据表> +- 影响与剩余项: + +## 合入与清理 + +- Review 记录:NOT_RUN(审阅智能体/平台身份、base/head、结论、问题闭合及重放依据) +- 冲突记录:NOT_RUN(最新目标同步、冲突处置、组合兼容核验及复审/补测) +- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果) +- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验) +- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置) + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-22T11:15:04.613824+00:00", + "command": "make precheck", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev", + "before_head": "3d3474a731f9ef3ee1f41935e63bca3afb70db56", + "after_head": "3d3474a731f9ef3ee1f41935e63bca3afb70db56", + "branch": "main", + "target_branch": "main", + "target_head": "3d3474a731f9ef3ee1f41935e63bca3afb70db56", + "root_target": "main", + "immediate_target": "main", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev'", + "[repo] branch=main head=3d3474a731", + "[remote] origin/main=3d3474a731 ahead=0 behind=0", + "[tree] clean submodules_drift=0 uninit=0", + "[worktrees] linked=6", + "[initiatives] local cards (not authoritative): none", + "[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)", + "[facts] policy/facts.md 6 条事实均在有效期内", + "[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md", + "[R0] 3d3474a731(完整:3d3474a731f9ef3ee1f41935e63bca3afb70db56)", + "[worktree] before-sync: recursive component checks passed", + "[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch", + "make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev'" + ], + "output_sha256": "87e58b98732700984715adb3f7a46aeacdc3749aacf51643f68a2ad9ede2451d", + "receipt_sha256": "72f14e2080b227c1fc4094a0a4f573b0dc12fb08ed30c86400265a610fb09b0d", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/72f14e2080b227c1fc4094a0a4f573b0dc12fb08ed30c86400265a610fb09b0d.json" +} +``` +| 同步(记录前对象) | c92443280ef8e9ed29bf5bf5250df650c01b4478 | c22b9fb34a3702191a9e5f6007f3538520db694b | 2026-10-22T13:10:38Z | + +## 闭合记录(card-autoclose 回填) + +- 依据:merge cb36b59accd0418c5ca952912d7c181b83038a4c(PR #702)已包含于 origin/main,分支成果已落盘;本段为合入后回填。 +- 合并主题:Merge pull request 'Use dynamic ports across the five test files that shared hardcoded bindings' (#702) from fix/svc-test-port-governance-batch1 into main +- 归档:随闭合移入 .task/archive/;任务执行期的过程证据见原任务记录与 PR。 diff --git a/.task/archive/fix-svc-test-port-governance-deploy-txn.md b/.task/archive/fix-svc-test-port-governance-deploy-txn.md new file mode 100644 index 00000000..5c78eb7a --- /dev/null +++ b/.task/archive/fix-svc-test-port-governance-deploy-txn.md @@ -0,0 +1,99 @@ +# fix/svc-test-port-governance-deploy-txn 任务卡 + +任务主记录:PR #705 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/705(merge f673c2a15a);状态:已完成(回填);owner:weichen + +- 权威仓:ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= cb36b59accd0418c5ca952912d7c181b83038a4c +- 工作区:/home/weichen/projects/Agent-System-Dev-wt-fix-svc-test-port-governance-deploy-txn(worktree);分支:fix/svc-test-port-governance-deploy-txn +- 分支类型:standalone;root_target:main;immediate_target:main;accepting_requirements:false +- 需求分流:verdict=NEW;selected_initiative=无;requirement_sha256=37c57479990e37eb048ba81763fe402e1e679a822679f315a4be18e4b965c333;triage_receipt=/tmp/plast-triage.json;receipt_sha256=d403df8b2564bf4e1fe16fb12ecc48cf31c25be4f7472e6b27bce6da3ecd93e0 + +- 授权与交付终点:<本次允许的动作与终点;未授权事项写明> +- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进) +- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md) +- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入) +- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位) + +## 目标与非目标 + +<预期结果、受影响入口/模块/契约;明确不做什么。> + +## Agent 实施收据 + +路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。 + +| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 | +|---|---|---|---|---|---|---|---|---|---|---| +| 待定义 | — | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | NOT_RUN | — | + +## 同步记录 + +| 节点 | sync_base_sha | tested_head_sha | checked_at | +|---|---|---|---| +| 启动 | cb36b59accd0418c5ca952912d7c181b83038a4c | cb36b59accd0418c5ca952912d7c181b83038a4c | 2026-10-22T13:39:45Z | + +## 证据(Evidence) + + +| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) | +|---|---|---|---|---|---| + +## 结果与验证 + +- 结果: +- 验证:<结论摘要 PASS/FAIL/SKIP/NOT_RUN 逐项;逐条证据由 make evidence 写入上方证据表> +- 影响与剩余项: + +## 合入与清理 + +- Review 记录:NOT_RUN(审阅智能体/平台身份、base/head、结论、问题闭合及重放依据) +- 冲突记录:NOT_RUN(最新目标同步、冲突处置、组合兼容核验及复审/补测) +- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果) +- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验) +- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置) + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-22T13:28:41.022157+00:00", + "command": "make precheck", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev", + "before_head": "cb36b59accd0418c5ca952912d7c181b83038a4c", + "after_head": "cb36b59accd0418c5ca952912d7c181b83038a4c", + "branch": "main", + "target_branch": "main", + "target_head": "cb36b59accd0418c5ca952912d7c181b83038a4c", + "root_target": "main", + "immediate_target": "main", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev'", + "[repo] branch=main head=cb36b59acc", + "[remote] origin/main=cb36b59acc ahead=0 behind=0", + "[tree] clean submodules_drift=0 uninit=0", + "[worktrees] linked=9", + "[recycle] merged un-recycled branches: fix/gitea-address-migration fix/svc-test-port-governance-batch1", + "[cards] 已合入未闭合任务卡:fix-svc-test-port-governance-batch1.md——make task-close 回填归档(主检出运行)", + "[initiatives] local cards (not authoritative): none", + "[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)", + "[facts] policy/facts.md 6 条事实均在有效期内", + "[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md", + "[R0] cb36b59acc(完整:cb36b59accd0418c5ca952912d7c181b83038a4c)", + "[worktree] before-sync: recursive component checks passed", + "[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch", + "make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev'" + ], + "output_sha256": "9f6260af81b0761dbb20afc5d145a9cb310303d426f655840a50f0879d81a49e", + "receipt_sha256": "eeb4f9ac9bdc0e30a0a7bbaa49176626bb8aca54a20f152b1771e1df8fd855d3", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/eeb4f9ac9bdc0e30a0a7bbaa49176626bb8aca54a20f152b1771e1df8fd855d3.json" +} +``` + +## 闭合记录(card-autoclose 回填) + +- 依据:merge f673c2a15a50bef2c55f2c79a17d7601f82c8a07(PR #705)已包含于 origin/main,分支成果已落盘;本段为合入后回填。 +- 合并主题:Merge pull request 'Use a dynamic port in the last hardcoded test binding' (#705) from fix/svc-test-port-governance-deploy-txn into main +- 归档:随闭合移入 .task/archive/;任务执行期的过程证据见原任务记录与 PR。 diff --git a/tests/test_svc_deploy_txn.py b/tests/test_svc_deploy_txn.py index a66ad23e..57e411c9 100644 --- a/tests/test_svc_deploy_txn.py +++ b/tests/test_svc_deploy_txn.py @@ -2,9 +2,13 @@ import importlib.util import json import os +import sys import unittest from pathlib import Path +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _port_alloc import free_test_port # noqa: E402 + SOURCE = Path(__file__).resolve().parents[1] _spec = importlib.util.spec_from_file_location( "tsi", SOURCE / "tests" / "test_svc_identity.py") @@ -46,7 +50,7 @@ class DeployTxnTests(unittest.TestCase): new.chmod(0o755) fx.stub.write_text(old.read_text()) fx.stub.chmod(0o755) - fx.seed_agent("txn", 9471) + fx.seed_agent("txn", free_test_port()) fx.start_ok("txn") pid = fx.pid_of("txn") script = fx.root / "scripts" / "svc.sh" @@ -68,7 +72,7 @@ class DeployTxnTests(unittest.TestCase): def test_forward_only_requires_confirm_and_backup(self): fx = self.fx - fx.seed_agent("fwd", 9472) + fx.seed_agent("fwd", free_test_port()) fx.start_ok("fwd") iid = json.loads((fx.runtime("fwd") / "manifest.json").read_text())["instance_id"] base = fx.stub.read_text() diff --git a/tests/test_svc_identity.py b/tests/test_svc_identity.py index d8466bb3..b75d16a8 100644 --- a/tests/test_svc_identity.py +++ b/tests/test_svc_identity.py @@ -12,10 +12,14 @@ import os import shutil import sqlite3 import subprocess +import sys import tempfile import unittest from pathlib import Path +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _port_alloc import free_test_port # noqa: E402 + SOURCE = Path(__file__).resolve().parents[1] SVC_SCRIPT = SOURCE / "scripts" / "svc.sh" @@ -56,7 +60,8 @@ class IdentityTests(tsr.SvcAutoRecoverTests): # ---- T1 连续裸启停:同一身份与数据根 ---- def test_T1_bare_restart_stable_identity(self): - self.seed_agent("id1", 9431) + port = free_test_port() + self.seed_agent("id1", port) self.start_ok("id1") m1 = self.manifest("id1") self.assertIsNotNone(m1, "start 应完成 manifest 迁移") @@ -79,7 +84,8 @@ class IdentityTests(tsr.SvcAutoRecoverTests): # ---- T2 只改端口:数据/DB 锚点不动 ---- def test_T2_port_change_keeps_anchors(self): - self.seed_agent("id2", 9432) + port = free_test_port() + self.seed_agent("id2", port) self.start_ok("id2") anchor = self.manifest("id2")["identity"] self.svc("id2", "stop") @@ -95,7 +101,8 @@ class IdentityTests(tsr.SvcAutoRecoverTests): # ---- T3 数据根漂移:拒绝启动,不新建空系统 ---- def test_T3_data_drift_rejected(self): - self.seed_agent("id3", 9434) + port = free_test_port() + self.seed_agent("id3", port) self.start_ok("id3") self.svc("id3", "stop") other = self.root / "other-data" @@ -111,7 +118,8 @@ class IdentityTests(tsr.SvcAutoRecoverTests): # ---- T4 迁移窗口与损坏:不猜测 ---- def test_T4a_leftover_new_file_ignored(self): - self.seed_agent("id4", 9435) + port = free_test_port() + self.seed_agent("id4", port) # 模拟 rename 前崩溃残留:.new 半成品必须被忽略/覆盖,不影响迁移结果 (self.runtime("id4") / "manifest.json.new").write_text("{ truncated") self.start_ok("id4") @@ -121,7 +129,8 @@ class IdentityTests(tsr.SvcAutoRecoverTests): self.svc("id4", "stop") def test_T4b_corrupt_manifest_blocks(self): - self.seed_agent("id5", 9436) + port = free_test_port() + self.seed_agent("id5", port) self.start_ok("id5") self.svc("id5", "stop") (self.runtime("id5") / "manifest.json").write_text("{ not json") @@ -133,7 +142,8 @@ class IdentityTests(tsr.SvcAutoRecoverTests): # ---- T5 marker/manifest 事件:缺一即阻塞,不自动新建 ---- def test_T5a_missing_marker_blocks(self): - self.seed_agent("id6", 9437) + port = free_test_port() + self.seed_agent("id6", port) self.start_ok("id6") self.svc("id6", "stop") m = self.manifest("id6") @@ -145,7 +155,8 @@ class IdentityTests(tsr.SvcAutoRecoverTests): self.assertFalse(self.running("id6")) def test_T5b_foreign_marker_blocks(self): - self.seed_agent("id7", 9438) + port = free_test_port() + self.seed_agent("id7", port) self.start_ok("id7") self.svc("id7", "stop") m = self.manifest("id7") @@ -158,7 +169,8 @@ class IdentityTests(tsr.SvcAutoRecoverTests): self.assertFalse(self.running("id7")) def test_T5c_lost_manifest_blocks_not_rebuild(self): - self.seed_agent("id8", 9439) + port = free_test_port() + self.seed_agent("id8", port) self.start_ok("id8") m0 = self.manifest("id8") iid = m0["instance_id"] @@ -178,7 +190,8 @@ class IdentityTests(tsr.SvcAutoRecoverTests): # ---- T6 cwd 无关:从别处调用身份不变 ---- def test_T6_cwd_independent_identity(self): - self.seed_agent("id9", 9440) + port = free_test_port() + self.seed_agent("id9", port) self.start_ok("id9") iid = self.manifest("id9")["instance_id"] self.svc("id9", "stop") @@ -320,7 +333,8 @@ class IdentityTests(tsr.SvcAutoRecoverTests): 旧服务继续运行、旧制品与登记不变(2026-10-12 rdm1/rdmcand 反例:停服→ 覆盖→启动即拒,只剩混合状态)。""" remote, old_sha, new_sha = self.make_rdm_repo(self.noid_stub()) - self.seed_agent("rdx", 9451) + port = free_test_port() + self.seed_agent("rdx", port) self.start_ok("rdx") # 已建立身份,有能力制品在运行 self.set_kv("rdx", "source", "rdm@%s" % old_sha) pid = self.pid_of("rdx") @@ -344,7 +358,8 @@ class IdentityTests(tsr.SvcAutoRecoverTests): """已建立身份后删行:update 在停服前拒绝,不自动补行,旧进程保持。 补行只走 adopt --confirm。""" old_sha, new_sha = self.make_src_repo() - self.seed_agent("rowx", 9452) + port = free_test_port() + self.seed_agent("rowx", port) self.start_ok("rowx") self.set_kv("rowx", "source", "dev@%s" % old_sha) db = Path(self.manifest("rowx")["identity"]["data_root"]) / "agent-system.db" @@ -364,7 +379,8 @@ class IdentityTests(tsr.SvcAutoRecoverTests): def test_U1_bare_start_missing_row_is_event_init_recovers(self): """普通启动只验证不补行:DB 行缺失 → 拒绝且不建空行(缺行即事件); 显式 init 补齐后恢复。""" - self.seed_agent("rowm", 9453) + port = free_test_port() + self.seed_agent("rowm", port) self.start_ok("rowm") iid = self.manifest("rowm")["instance_id"] self.svc("rowm", "stop") @@ -416,7 +432,8 @@ class IdentityTests(tsr.SvcAutoRecoverTests): def test_U1_adopt_identityless_artifact_reports_pending(self): """已装制品无 identity 能力:adopt 的 ensure 静默跳过,marker 留在 pending—— 输出必须如实报 pending,不得宣称 db_marker=established。""" - self.seed_agent("adx", 9454) + port = free_test_port() + self.seed_agent("adx", port) self.start_ok("adx") iid = self.manifest("adx")["instance_id"] self.svc("adx", "stop") @@ -437,7 +454,8 @@ class IdentityTests(tsr.SvcAutoRecoverTests): cand.chmod(0o755) remote, old_sha, _ = self._make_track_repo(cand, "agent-system") self.git_ok(self.root, "clone", str(remote), str(self.root / "srccache")) - self.seed_agent("scx", 9455) + port = free_test_port() + self.seed_agent("scx", port) self.start_ok("scx") self.set_kv("scx", "source", "dev@%s" % old_sha) mpath = self.runtime("scx") / "manifest.json" @@ -459,7 +477,8 @@ class IdentityTests(tsr.SvcAutoRecoverTests): self.svc("scx", "stop") def test_I3_start_ensures_sqlite_identity(self): - self.seed_agent("idb1", 9441) + port = free_test_port() + self.seed_agent("idb1", port) self.start_ok("idb1") iid = self.manifest("idb1")["instance_id"] rows = self.db_identity_row("idb1") @@ -470,7 +489,8 @@ class IdentityTests(tsr.SvcAutoRecoverTests): self.svc("idb1", "stop") def test_I3_wrong_db_identity_blocks_start(self): - self.seed_agent("idb2", 9442) + port = free_test_port() + self.seed_agent("idb2", port) self.start_ok("idb2") iid = self.manifest("idb2")["instance_id"] self.svc("idb2", "stop") diff --git a/tests/test_svc_lifecycle_isolation.py b/tests/test_svc_lifecycle_isolation.py index f440c66f..7fd635c9 100644 --- a/tests/test_svc_lifecycle_isolation.py +++ b/tests/test_svc_lifecycle_isolation.py @@ -1,9 +1,14 @@ """生命周期隔离:调用方的 AGENT_SYSTEM_* 不得进入 serve,serve.env 为 0600。""" import importlib.util import json +import os import shutil +import sys from pathlib import Path +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _port_alloc import free_test_port # noqa: E402 + SOURCE = Path(__file__).resolve().parents[1] _spec = importlib.util.spec_from_file_location("tsr", SOURCE / "tests" / "test_svc_recover.py") tsr = importlib.util.module_from_spec(_spec) @@ -17,7 +22,8 @@ class LifecycleIsolationTests(tsr.SvcAutoRecoverTests): self.root / "scripts" / "svc_manifest.py") def test_caller_identity_env_does_not_reach_serve(self): - self.seed_agent("iso", 9461) + port = free_test_port() + self.seed_agent("iso", port) env = dict(self.env) env["AGENT_SYSTEM_DSN"] = "postgres://leak-user:leak-pass@127.0.0.1/leak" env["AGENT_SYSTEM_DATA"] = "/tmp/leak-data" @@ -38,7 +44,8 @@ class LifecycleIsolationTests(tsr.SvcAutoRecoverTests): self.svc("iso", "stop") def test_two_bare_restarts_keep_identity(self): - self.seed_agent("iso2", 9462) + port = free_test_port() + self.seed_agent("iso2", port) self.svc("iso2", "start") manifest = json.loads((self.runtime("iso2") / "manifest.json").read_text()) iid = manifest["instance_id"] diff --git a/tests/test_svc_rdm_admin.py b/tests/test_svc_rdm_admin.py index 0c930d3f..d336ede4 100644 --- a/tests/test_svc_rdm_admin.py +++ b/tests/test_svc_rdm_admin.py @@ -11,12 +11,17 @@ 夹具复用 tests/test_svc_recover.py(桩不需要运行——台账操作是 SQLite 直读直写)。 """ import importlib.util +import os import shutil import sqlite3 import subprocess +import sys import unittest from pathlib import Path +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _port_alloc import free_test_port # noqa: E402 + SOURCE = Path(__file__).resolve().parents[1] _spec = importlib.util.spec_from_file_location("tsr", SOURCE / "tests" / "test_svc_recover.py") @@ -64,21 +69,24 @@ class RdmAdminTests(tsr.SvcAutoRecoverTests): # ---- show ---- def test_show_prints_ledger_and_flags_unknown_owner(self): - self.seed_agent("ra1", 9461) + port = free_test_port() + self.seed_agent("ra1", port) self.seed_dbs("ra1", ledger_owner="u7-admin", base_owners=("weichen",)) out = self.svc("ra1", "rdm-admin", "show").stdout self.assertIn("RDM 域管理员 u7-admin", out) self.assertIn("不在基座用户表", out) # u7-admin 不在 users → 残留警示 def test_show_known_owner_no_warning(self): - self.seed_agent("ra2", 9462) + port = free_test_port() + self.seed_agent("ra2", port) self.seed_dbs("ra2", ledger_owner="weichen") out = self.svc("ra2", "rdm-admin").stdout # 缺省子命令 = show self.assertIn("RDM 域管理员 weichen", out) self.assertNotIn("不在基座用户表", out) def test_show_empty_ledger_hint(self): - self.seed_agent("ra3", 9463) + port = free_test_port() + self.seed_agent("ra3", port) self.seed_dbs("ra3", ledger_owner=None) out = self.svc("ra3", "rdm-admin").stdout self.assertIn("台账为空", out) @@ -87,7 +95,8 @@ class RdmAdminTests(tsr.SvcAutoRecoverTests): # ---- transfer ---- def test_transfer_rewrites_single_row_with_provenance(self): - self.seed_agent("ra4", 9464) + port = free_test_port() + self.seed_agent("ra4", port) self.seed_dbs("ra4", ledger_owner="u7-admin") out = self.svc("ra4", "rdm-admin", "transfer", "--to", "weichen").stdout self.assertIn("受信部署通道", out) @@ -97,13 +106,15 @@ class RdmAdminTests(tsr.SvcAutoRecoverTests): self.assertEqual(added_by, "svc:rdm-admin:u7-admin") # 前任可追溯 def test_transfer_idempotent_when_already_owner(self): - self.seed_agent("ra5", 9465) + port = free_test_port() + self.seed_agent("ra5", port) self.seed_dbs("ra5", ledger_owner="weichen") out = self.svc("ra5", "rdm-admin", "transfer", "--to", "weichen").stdout self.assertIn("已是 weichen", out) def test_transfer_rejects_owner_missing_from_base_users(self): - self.seed_agent("ra6", 9466) + port = free_test_port() + self.seed_agent("ra6", port) self.seed_dbs("ra6", ledger_owner="weichen") proc = self.svc("ra6", "rdm-admin", "transfer", "--to", "nobody", check=False) self.assertEqual(proc.returncode, 1, proc.stdout) @@ -111,7 +122,8 @@ class RdmAdminTests(tsr.SvcAutoRecoverTests): self.assertEqual(self.ledger_owner("ra6")[0], "weichen") # 台账未被动 def test_transfer_rejects_empty_ledger(self): - self.seed_agent("ra7", 9467) + port = free_test_port() + self.seed_agent("ra7", port) self.seed_dbs("ra7", ledger_owner=None) proc = self.svc("ra7", "rdm-admin", "transfer", "--to", "weichen", check=False) self.assertEqual(proc.returncode, 1, proc.stdout) @@ -120,7 +132,8 @@ class RdmAdminTests(tsr.SvcAutoRecoverTests): # ---- 绑定合同与用法 ---- def test_refuses_when_rdm_db_missing(self): - self.seed_agent("ra8", 9468) + port = free_test_port() + self.seed_agent("ra8", port) self.seed_dbs("ra8") # 建基座库后删 rdm.db (self.runtime("ra8") / "data" / "rdm.db").unlink() proc = self.svc("ra8", "rdm-admin", check=False) @@ -128,13 +141,15 @@ class RdmAdminTests(tsr.SvcAutoRecoverTests): self.assertIn("无 RDM 域库", proc.stdout) def test_refuses_when_base_db_missing(self): - self.seed_agent("ra9", 9469) + port = free_test_port() + self.seed_agent("ra9", port) proc = self.svc("ra9", "rdm-admin", check=False) self.assertEqual(proc.returncode, 1, proc.stdout) self.assertIn("既有库不存在", proc.stdout) def test_unknown_subcommand_usage_exit_2(self): - self.seed_agent("raa", 9470) + port = free_test_port() + self.seed_agent("raa", port) proc = self.svc("raa", "rdm-admin", "bogus", check=False) self.assertEqual(proc.returncode, 2, proc.stdout) self.assertIn("用法", proc.stdout) @@ -142,14 +157,16 @@ class RdmAdminTests(tsr.SvcAutoRecoverTests): # ---- status 巡检行 ---- def test_status_lists_rdm_admin_row_and_warning(self): - self.seed_agent("rab", 9471) + port = free_test_port() + self.seed_agent("rab", port) self.seed_dbs("rab", ledger_owner="u7-admin", base_owners=("weichen",)) out = self.svc("rab", "status").stdout self.assertIn("RDM 域管理员 u7-admin", out) self.assertIn("不在基座用户表", out) def test_status_silent_without_rdm_db(self): - self.seed_agent("rac", 9472) + port = free_test_port() + self.seed_agent("rac", port) self.seed_dbs("rac") (self.runtime("rac") / "data" / "rdm.db").unlink() out = self.svc("rac", "status").stdout diff --git a/tests/test_svc_u2.py b/tests/test_svc_u2.py index eff05791..1d2c8d87 100644 --- a/tests/test_svc_u2.py +++ b/tests/test_svc_u2.py @@ -12,9 +12,13 @@ import os import shlex import shutil import subprocess +import sys import unittest from pathlib import Path +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _port_alloc import free_test_port # noqa: E402 + SOURCE = Path(__file__).resolve().parents[1] _spec = importlib.util.spec_from_file_location("tsr", SOURCE / "tests" / "test_svc_recover.py") @@ -106,7 +110,8 @@ class U2ReleaseTests(tsi.IdentityTests): clock.chmod(0o755) self.env["PATH"] = str(clock_bin) + os.pathsep + self.env["PATH"] self.make_src_repo() # 预克隆 srccache(本用例走 --from local,仅保环境一致) - self.seed_agent("u2a", 9461) + port = free_test_port() + self.seed_agent("u2a", port) self.start_ok("u2a") # legacy 运行中(期望态=running) self.assertTrue(self.running("u2a")) @@ -147,7 +152,8 @@ class U2ReleaseTests(tsi.IdentityTests): # ---- T2:构建失败 → 旧服务保持运行、current 不动 ---- def test_U2_build_failure_keeps_old_running(self): - self.seed_agent("u2b", 9462) + port = free_test_port() + self.seed_agent("u2b", port) self.set_makefile(self.stub) self.svc("u2b", "deploy", "--from", "local") cur1 = self.cur_release("u2b") @@ -164,7 +170,8 @@ class U2ReleaseTests(tsi.IdentityTests): # ---- T3:中断切换(journal switched)→ start 入口回退并恢复 ---- def test_U2_journal_interrupt_resolved_on_start(self): - self.seed_agent("u2c", 9463) + port = free_test_port() + self.seed_agent("u2c", port) self.set_makefile(self.stub) self.svc("u2c", "deploy", "--from", "local") v1 = self.cur_release("u2c") @@ -190,7 +197,8 @@ class U2ReleaseTests(tsi.IdentityTests): # ---- T4:schema 前进 → 回退阻断,不触碰业务库 ---- def test_U2_schema_advance_blocks_rollback(self): - self.seed_agent("u2d", 9464) + port = free_test_port() + self.seed_agent("u2d", port) self.set_makefile(self.stub) self.svc("u2d", "deploy", "--from", "local") v1 = self.cur_release("u2d") @@ -230,7 +238,8 @@ class U2ReleaseTests(tsi.IdentityTests): # ---- T5:legacy 布局未迁移 → 裸 start/restart 仍走原路径(兼容) ---- def test_U2_fault_after_switch_rolls_back_on_next_start(self): - self.seed_agent("u2f", 9466) + port = free_test_port() + self.seed_agent("u2f", port) self.set_makefile(self.stub) self.svc("u2f", "deploy", "--from", "local") v1 = self.cur_release("u2f") @@ -248,7 +257,8 @@ class U2ReleaseTests(tsi.IdentityTests): self.svc("u2f", "stop") def test_U2_health_timeout_rolls_back(self): - self.seed_agent("u2g", 9467) + port = free_test_port() + self.seed_agent("u2g", port) self.set_makefile(self.stub) self.svc("u2g", "deploy", "--from", "local") v1 = self.cur_release("u2g") @@ -277,7 +287,8 @@ class U2ReleaseTests(tsi.IdentityTests): return p def test_U2_legacy_layout_bare_lifecycle_still_works(self): - self.seed_agent("u2e", 9465) + port = free_test_port() + self.seed_agent("u2e", port) self.start_ok("u2e") self.assertFalse((self.runtime("u2e") / "current").exists()) self.assertTrue((self.runtime("u2e") / "bin" / "agent-system").exists()) diff --git a/tests/test_svc_user_binding.py b/tests/test_svc_user_binding.py index a8b3dfaf..e266ca05 100644 --- a/tests/test_svc_user_binding.py +++ b/tests/test_svc_user_binding.py @@ -9,9 +9,13 @@ import importlib.util import json import os import shutil +import sys import unittest from pathlib import Path +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from _port_alloc import free_test_port # noqa: E402 + SOURCE = Path(__file__).resolve().parents[1] _spec = importlib.util.spec_from_file_location("tsr", SOURCE / "tests" / "test_svc_recover.py") @@ -57,7 +61,8 @@ class UserBindingTests(tsr.SvcAutoRecoverTests): # ---- T1 合法 manifest:user list 绑定 data_root + --open-existing ---- def test_T1_user_list_binds_manifest_data(self): - self.seed_agent("ub1", 9441) + port = free_test_port() + self.seed_agent("ub1", port) self.start_ok("ub1") self.svc("ub1", "stop") root = self.manifest("ub1")["identity"]["data_root"] @@ -74,7 +79,8 @@ class UserBindingTests(tsr.SvcAutoRecoverTests): # ---- T2 禁止覆盖 --data;未绑定默认目录拒绝 ---- def test_T2_reject_override_data_flag(self): - self.seed_agent("ub2", 9442) + port = free_test_port() + self.seed_agent("ub2", port) self.start_ok("ub2") self.svc("ub2", "stop") self.touch_db("ub2") @@ -106,7 +112,8 @@ class UserBindingTests(tsr.SvcAutoRecoverTests): # ---- T3 删除 data 根:OpenExisting 失败,不建空库 ---- def test_T3_missing_data_root_rejected(self): - self.seed_agent("ub3", 9444) + port = free_test_port() + self.seed_agent("ub3", port) self.start_ok("ub3") self.svc("ub3", "stop") root = Path(self.manifest("ub3")["identity"]["data_root"]) @@ -124,7 +131,8 @@ class UserBindingTests(tsr.SvcAutoRecoverTests): # ---- T4 隐藏 flock:user/deploy 拒绝;已运行服务仍在 ---- def test_T4_no_flock_rejects_user_and_deploy(self): - self.seed_agent("ub4", 9445) + port = free_test_port() + self.seed_agent("ub4", port) self.start_ok("ub4") self.assertTrue(self.running("ub4")) self.touch_db("ub4") @@ -158,7 +166,8 @@ class UserBindingTests(tsr.SvcAutoRecoverTests): # ---- T5 recovery U1 身份冒烟(完整套件见 test_svc_identity)---- def test_T5_identity_regression_smoke(self): - self.seed_agent("ub5", 9446) + port = free_test_port() + self.seed_agent("ub5", port) self.start_ok("ub5") iid = self.manifest("ub5")["instance_id"] self.svc("ub5", "stop")