Repository navigation
test(contract): keep the mode-000 read checks POSIX-only - #6007
huangruiteng merged 1 commit into
Conversation
`os.geteuid()` does not exist on Windows, and two tests used it to skip a root host that can always read a mode-000 file. The contract-scan one called it in a module-level decorator, so a plain `pytest tests/` on Windows stopped at `Interrupted: 1 error during collection` and ran none of the roughly 19k collected tests. Guard both sites on `os.name == "nt"` first. Windows cannot express the mode-000 read denial those assertions need, and the short-circuit leaves the existing root check exactly as it was on POSIX hosts. Verified on Windows: the suite collects with 0 errors and both tests report as skipped. The POSIX path is unchanged because the new first condition is false there. Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent — gpt-6.1-sol / OpenAI; runtime_reported; reasoning_effort=xhigh.
动机
在 Windows 上运行仓库测试的贡献者会在收集测试时遇到异常,导致本来能运行的其他测试也没有机会执行。
例如贡献者启动 pytest,旧装饰器读取 Windows 不存在的用户权限函数,整个收集流程中断;新版本先识别 Windows,跳过只能用 POSIX 文件权限构造的拒读场景,让其他测试继续进入执行。
独立探针复现了旧版缺少 geteuid 时的两处异常,新 head 在 Windows 条件下不访问该函数;本机非 root 的真实拒读与恢复测试在 base/head 都通过。
本 PR 只修复两处测试的平台前置条件,不改运行时权限,也不宣称原生 Windows 全库、符号链接权限或路径分隔符问题已经验收。
精确 head:ec9d185a4455ecba4c9d64940b3ff0fc95b3f461;不可变 merge base:fac40bbc43ff17d0c0d573af69204cb73b36e842。当前 main 已另有推进,所有补丁前后比较固定在这两个版本。
改动思路
复用两处已有的测试前置判断,先检查 os.name == "nt",再执行原 os.geteuid() == 0。Python 的 or 短路使 Windows 不访问 Unix 专用函数;POSIX 下第一项为 false,原 root 与非 root 分支保持。没有增加平台抽象、测试副本或生产行为。
实际拒读路径仍由原 scanner 和 TypeScript preference owner 处理:测试给隔离文件或目录设置 mode 000,验证拒读被披露;finally 恢复权限,再用真实 CLI 读取当前偏好,确认不会沿用失败或缓存内容。这里没有操作活动 Goal。
具体改动
- scanner 拒读测试装饰器:改为 Windows 条件在前的
skipif,解决模块导入时的缺函数异常,原拒读断言与权限恢复不变。 - 偏好拒读与恢复测试:函数开头采用同一判断,journal/namespace 两个参数实例继续调用真实 semantic-preference 与 quota 入口。
完整 diff 是这两文件 +6/-3,均为测试前置条件与跳过原因。没有独立的 LoopX 书面规范、关联 RFC/issue 或维护者 review frame;本次按可复现缺陷和原测试契约评审。Python 官方 os 文档 将 geteuid 限定于 Unix;Windows chmod 说明 也说明它不能提供这里的 POSIX mode-bit 拒读条件。原非 root 拒读语义保留,Windows 收集短路修复已独立验证。没有把作者的 Windows 全库数量当作本轮证据。
对主干的风险
相同命令 uv run --extra test python -m pytest -q tests/test_contract_scan_unreadable_files.py tests/control_plane/test_agent_preferences.py:base 14 passed,head 14 passed。本机 macOS 非 root uid501,三项拒读实例实际执行,没有被跳过;真实 scanner、CLI、TS owner 与权限恢复均由原断言覆盖。head 两文件真实 pytest 收集 14 项,没有收集错误。
另对精确源文件执行独立 guard 探针,只把测试模块自己的 os import 替换为 Windows 缺少 geteuid、POSIX root、POSIX 非 root 三种输入,不替换产品 IO。base 在 Windows 输入的模块装饰器和函数入口各出现一次 AttributeError;head 装饰器定义全部五项 scanner 测试且标记目标跳过,偏好函数在创建 fixture 前由 pytest 跳过,两处均不访问 geteuid。root 分支仍跳过;非 root 装饰器仍可执行,完整函数由前述真实测试覆盖。此探针证明短路条件,不冒充原生 Windows ACL 或全库执行。
原生 premerge 4 项直接检查通过、0 项 catalog 被选中,Ruff、diff hygiene、DCO 和两文件 public boundary 扫描通过。没有失败或跳过的必需检查;首次探针因评审脚本导入路径缺少仓库而在两端失败,修正脚本路径后重跑,原失败保留。未跑原生 Windows、全库 suite、安装态 App/Lark;作者提到的 Windows 符号链接、路径分隔符、mypy 与文件占用问题不在改动中,本轮未独立归因或宣称修复。没有查询、轮询或等待远端 CI。
现有覆盖检索确认这两个拒读场景仍各有自己的生产消费者;同作者公开列表本轮仅返回 #6007,未发现重复测试或同形批量拆分。修复现有回归覆盖有实际维护价值。typed-state、domain-neutrality、default-off、authority、guidance-vs-obligation 透镜均检查完整 diff:这里没有新增协议或机器义务,也没有运行时默认、激活范围或权限变化。
我的整体评价
APPROVE,无阻塞发现。 goal_achieved 只关闭这两处测试前置条件缺陷;原生 Windows 整体资格和更大路线图没有因此完成。long_horizon 与产品 user_experience 是 not_applicable:检查的生产权限、持久状态、调度和 frontend/Lark 均未变化;贡献者测试收集的改善已通过独立缺函数输入证明。
Future-facing pass 检查了相邻平台判断与原拒读 owner:保留两处局部判断即可,不需要新共享框架;原失败披露、权限恢复和类型化决策归属保持。历史 recovery-RFC 的阶段建议不适用于这次测试维护,也不赋予新动作权限。合并资格与合并授权分别判断;本轮不合并,更换 head 后需重新评审。
English verdict: APPROVE — ec9d185. Independent immutable base/head probes reproduce both missing-geteuid failures and verify Windows short-circuit plus POSIX root parity; all 14 affected tests pass on both real non-root macOS arms, including physical denial and CLI recovery. Native premerge, Ruff, DCO and public-boundary checks pass. Native Windows/full-suite execution remains unverified; remote CI was not consulted and merge authority is separate.
JasonBuildAI
left a comment
There was a problem hiding this comment.
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
Reviewer: model_agent (Codex, OpenAI), self_reported
Reviewed exact head: ec9d185 (loopx-project/loopx #6007, base main).
动机
本 PR 修复的是一个已复现的跨平台缺陷:Windows 上收集整个 tests/ 目录会在导入阶段被中断,本地开发者拿不到任何测试结果。
- 受影响的是在原生 Windows 主机上本地运行 pytest 整套测试的贡献者与开发者;仓库 CI 的 windows-powershell 泳道只运行一份显式列出的用例清单,因此不会触发这个模块级错误。
- 修复前:Windows 上执行 pytest tests/ 得到 18982 个用例被收集、3 个错误、Interrupted,已收集的用例零执行。修复后:同一命令在同一主机上仍然只有两个与本机 Node 版本相关的错误,os.geteuid 引起的采集错误消失,该模块可被收集,两个受保护测试报告 skipped。
- 可观察结果是:Windows 主机上该模块不再中断采集,模块内两个依赖 mode 000 读取拒绝的断言在 Windows 上有意识地跳过,而 POSIX 主机上的求值结果逐字保持原样。
- 本次不做的事:不改变任何产品代码、CLI 契约、权限边界或持久状态;不修复同一个文件里既有的其他 Windows 失败;不为整个 tests/ 目录新增系统性的跨平台收集保障。
- 仍存在的缺口:该文件在 Windows 上还有 3 个失败用例,即两个需要符号链接创建权限的 dangling-symlink 用例,以及第 109 行把扫描命中与硬编码斜杠分隔符比较的断言,所以 Windows 本地跑通整套测试这一目标只完成了采集层面。
改动思路
改动落在既有的 pytest 守卫这一最小边界上,不新增模块、配置项或抽象。唯一权威输入是宿主平台能力:os.name 与 os.geteuid 是否存在;决策 owner 是测试自身的 skip 表达式,副作用只是把用例标记为 skipped,没有新的持久状态或外部 IO。
作者选择 os.name == nt 作为短路首项,与 tests/ 目录里更普遍的跨平台守卫写法一致,也让 POSIX 上的求值退化为原来的 os.geteuid() == 0,属于可逆的行为保持型改动;另一种写法 hasattr(os, geteuid) 在仓库中只有个别先例,语义相同但没有额外收益,因此现有 owner 已经足够,不需要新的守卫 helper。
具体改动
评审的精确头为 ec9d185,基线为 fac40bb,共 2 个文件、+6/-3,全部属于 tests_or_fixtures 类别,没有生产代码、文档或生成物改动。
关键代码讲解
tests/test_contract_scan_unreadable_files.py:70 的 test_scan_public_boundary_reports_unreadable_file:模块级装饰器由 os.geteuid() == 0 改为 os.name == nt or os.geteuid() == 0。由于装饰器在导入期求值,这一行原先在 Windows 上直接抛 AttributeError,导致整个收集被 Interrupted;短路求值后 Windows 不再触碰不存在的属性。
tests/control_plane/test_agent_preferences.py:252 的 test_permission_denied_hook_is_not_empty_and_recovers:函数体内同样的判断改为同一表达式,避免在 Windows 上执行到 mode 000 读取拒绝断言并失败。
规格基准:本改动没有书面规格,属于自包含的已复现缺陷修复,因此按 no_spec 处理;作者在 PR 描述中给出的判据表与上面两处符号一一对应。
对主干的风险
最强的回归场景是 POSIX 上原本应当跳过的 root 主机或应当执行的普通用户主机:新表达式的第一项在 POSIX 恒为 False,求值结果与改动前完全一致,我在本机也确认了 ruff check 在头版本上通过,ruff format 的差异在基线上同样存在,属于既有状态而非本次引入。
第二个风险是覆盖率语义:Windows 上这两个断言现在是显式跳过而不是通过,等于承认 Windows 无法表达 mode 000 读取拒绝;这是有意的平台边界,但也意味着该不变式在 Windows 上没有覆盖,不能把本 PR 读成 Windows 上的权限语义已被验证。
回滚成本极低:改动只影响 skip 判定,撤销即为恢复原行为;没有持久状态、迁移或兼容面需要处理。
我的整体评价
结论是值得合并:这是一个范围克制、可独立验证、可独立回滚的测试基建修复,直接移除了 Windows 贡献者无法运行整套本地测试的硬阻塞,对产品行为、权限边界和主干语义零影响,并复用了仓库既有的跨平台守卫约定。
合并前仍有两项与代码无关的门禁需要满足:本 PR 来自 fork,三个工作流运行(Sign-off、Dependency Review、Python Tests)目前都是 action_required 且没有任何 job 执行,因此 Sign-off 与 merge-gate 这两个必需检查尚未产生证据,合并就绪性不能由 CI 证明;同时分支落后 main 一个提交(merge_state 为 BEHIND,即 #6006)。这两点都属于合并门禁而非评审阻塞项。
剩余风险与后继:作者已如实披露同文件内另外 3 个 Windows 失败,它们不阻塞本次合并,但需要在后继工作中给出平台化期望或把该模块明确标记为 POSIX-only。请以我方独立复现为准:采集错误确实消失,两个受保护测试确实在 Windows 上跳过。
English verdict: APPROVE
Goal And Delivered Outcome
Outcome basis / optional anchor: self-contained reproduced defect, so no issue or roadmap card is needed for this class of repair.
Goal/source and gap:
os.geteuid()does not exist on Windows, and two permission tests used it to skip a host that can read a mode-000 file. The contract-scan one sits in a module-level decorator, so importing the module raisedAttributeError: module 'os' has no attribute 'geteuid'and pytest stopped the whole run withInterrupted: 1 error during collection. A Windows contributor could not runpytest tests/at all: the suite was collected and then discarded.Observable before -> after, with the validation row that proves it:
python -m pytest tests/ --collect-only -qwent from18997 tests collected, 1 errorplusInterrupted: 1 error during collectionto19024 tests collectedwith no errors, and both tests now reportskippedon Windows.Issue/task and intended base: no separate issue (self-contained defect). Intended base:
loopx-project/loopxmainatfac40bbc4.Author Declaration
Implemented against
os.name == "nt"/os.name != "posix"guards used throughouttests/).tests/tests/test_contract_scan_unreadable_files.py:70python -m pytest tests/ --collect-only -q->19024 tests collected, 0 errorsos.name == "nt"is false on POSIX, so the expression still evaluates toos.geteuid() == 0tests/control_plane/test_agent_preferences.py:251SKIPPED [...] mode 000 read denial needs a non-root POSIX hostruff,mypy,loopx canary premerge --from-git-diff,loopx checkandgit diff --check. I deliberately left the other, unrelated Windows issues in the same module alone; see Scope below.Scope And Continuation
tests/test_contract_scan_unreadable_files.pyand are intentionally untouched: the two dangling-symlink tests needSeCreateSymbolicLinkPrivilege, and the assertion at line 109 compares a scan hit against a hard-coded/separator although hits are returned with the platform separator. Neither is caused by, or fixed by, this change.Validation
ec9d185a4staticpassedpython -m ruff check tests loopx/canary loopx/control_plane loopx/domain_packs loopx/presentation-> all checks passedstaticfailedpython -m mypyreports one Windows-only error inloopx/control_plane/effect_runtime.py:432(signal.WNOHANGis absent on Windows). It reproduces unchanged on unmodifiedmain, and that file is not touched here;mypyruns on Linux in CI.unitpassedpython -m pytest -q tests/test_contract_scan_unreadable_files.py tests/control_plane/test_agent_preferences.py -k "unreadable or dangling or permission_denied"-> the two guarded tests reportskippedand collection no longer errorsunitpassedpython -m pytest tests/ --collect-only -q->19024 tests collected, 0 errorsintegrationnot_applicablemanualfailedpython examples/control_plane/cli-output-budget-regression-smoke.pycannot finish on this Windows host: temp cleanup hitsWinError 32(file in use). The identical failure reproduces on unmodifiedmain, so it is a pre-existing local limitation, not a regression.manualpassedloopx canary premerge --from-git-diff --git-diff-base upstream/main-> passed;loopx check --scan-path loopx/ --scan-path tests/ --scan-path examples/ --scan-path docs/-> public boundary scan clean;git diff --check-> cleanpytestshards exercise; the Windows-only failures above are pre-existing and were verified against unmodifiedmain.Frontend / Visual Evidence
Type of Change
LoopX Area
Technical Direction
Shared-authority RFC fixture impact
Boundary Checklist
none.Signed-off-bytrailer (git commit -s).Thanks for taking a look. I am happy to adjust the scope or the wording if that makes it easier to review.