工程(索引): 索引只维护会提交内容;修复独占锁内收集自锁
- 维护索引按 git 视角枚举(已跟踪 + 未跟踪且未忽略):被 .gitignore 忽略的运行痕迹与 过程性检查报告不再进入目录派生或影响索引门禁;非 git 场景退回原磁盘枚举 - conftest 验证闸门在 `验证锁 --执行` 启动的会话内不再取共享锁,修复 `make 索引生成` 在独占锁内收集用例时的自锁(原 180s 超时失败 → 现 6s 通过) - .gitignore 登记过程性检查报告与工具会话缓存(.zcode/):可复跑,不入历史 - 新增两条测试:忽略内容不参与目录派生、独占锁内收集不自锁
This commit is contained in:
parent
9712589bfe
commit
8a44364539
4
.gitignore
vendored
4
.gitignore
vendored
@ -49,3 +49,7 @@ web/playwright-report/
|
|||||||
|
|
||||||
# TypeScript增量编译缓存(已有跟踪项需另获索引操作授权后取消跟踪)
|
# TypeScript增量编译缓存(已有跟踪项需另获索引操作授权后取消跟踪)
|
||||||
web/*.tsbuildinfo
|
web/*.tsbuildinfo
|
||||||
|
|
||||||
|
# 工具会话缓存与过程性检查报告:可复跑,不入历史
|
||||||
|
docs/实现回顾/专项检查-报告-*.md
|
||||||
|
.zcode/
|
||||||
|
|||||||
@ -39,7 +39,7 @@ def 准备验证闸门(session: pytest.Session) -> None:
|
|||||||
"""采集前持锁;外部环境层再核对源码身份,不一致立即失败,不等到几百个用例报假错。"""
|
"""采集前持锁;外部环境层再核对源码身份,不一致立即失败,不等到几百个用例报假错。"""
|
||||||
from contextlib import ExitStack
|
from contextlib import ExitStack
|
||||||
|
|
||||||
from 验证锁 import 持有, 源码身份核对, 登记说明
|
from 验证锁 import 已在独占锁内, 持有, 源码身份核对, 登记说明
|
||||||
|
|
||||||
# 离线层按用例显式注入源码资源,不依赖已安装资源身份;只有外部环境层才需要同身份。
|
# 离线层按用例显式注入源码资源,不依赖已安装资源身份;只有外部环境层才需要同身份。
|
||||||
if session.config.getoption("--外部环境"):
|
if session.config.getoption("--外部环境"):
|
||||||
@ -54,7 +54,10 @@ def 准备验证闸门(session: pytest.Session) -> None:
|
|||||||
|
|
||||||
说明 = f"pytest {' '.join(session.config.args) or '全部'}(pid {os.getpid()})"
|
说明 = f"pytest {' '.join(session.config.args) or '全部'}(pid {os.getpid()})"
|
||||||
登记说明(说明)
|
登记说明(说明)
|
||||||
_验证闸门["栈"].enter_context(持有(说明))
|
# 由 `验证锁 --执行` 启动时父进程已持独占写锁,再取共享锁会自锁;
|
||||||
|
# `make 索引生成` 就在锁内收集用例,走的正是这条路径。
|
||||||
|
if not 已在独占锁内():
|
||||||
|
_验证闸门["栈"].enter_context(持有(说明))
|
||||||
|
|
||||||
|
|
||||||
def pytest_addoption(parser: pytest.Parser) -> None:
|
def pytest_addoption(parser: pytest.Parser) -> None:
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@ -15,7 +16,7 @@ import pytest
|
|||||||
|
|
||||||
仓库根 = Path(__file__).resolve().parents[2]
|
仓库根 = Path(__file__).resolve().parents[2]
|
||||||
sys.path.insert(0, str(仓库根 / "工具"))
|
sys.path.insert(0, str(仓库根 / "工具"))
|
||||||
from 维护索引 import 检查技能目录 # noqa: E402
|
from 维护索引 import 写入目录, 检查技能目录, 检查派生目录 # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
def _写能力样本(根: Path, 相对: str, 身份: str, 名称: str) -> Path:
|
def _写能力样本(根: Path, 相对: str, 身份: str, 名称: str) -> Path:
|
||||||
@ -155,6 +156,35 @@ def test_skill_directory_without_skill_file_fails__e8450b(tmp_path: Path) -> Non
|
|||||||
assert any("缺少 SKILL.md" in p for p in 问题), 问题
|
assert any("缺少 SKILL.md" in p for p in 问题), 问题
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.case_id(
|
||||||
|
"TC-7b1e4d9c35a2",
|
||||||
|
environment="离线,临时 git 工作树",
|
||||||
|
when="读取或生成登记资源及导航。",
|
||||||
|
contract="docs/系统架构/新版设计/编排与能力资源.md",
|
||||||
|
)
|
||||||
|
def test_索引只覆盖会提交内容而忽略运行痕迹__7b1e4d(tmp_path: Path) -> None:
|
||||||
|
"""被 .gitignore 忽略的运行痕迹不进入目录派生,也不影响索引一致性。"""
|
||||||
|
根 = tmp_path
|
||||||
|
subprocess.run(["git", "init", "-q"], cwd=根, check=True)
|
||||||
|
(根 / ".gitignore").write_text("docs/噪音/\n", encoding="utf-8")
|
||||||
|
(根 / "docs").mkdir()
|
||||||
|
(根 / "docs" / "目录.md").write_text(
|
||||||
|
"| 名称 | 相对地址 | 内容描述 | 使用场景 | 使用要求 |\n"
|
||||||
|
"|------|----------|----------|----------|----------|\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
(根 / "docs" / "真实.md").write_text("# 真实\n\n正文。\n", encoding="utf-8")
|
||||||
|
(根 / "docs" / "噪音").mkdir()
|
||||||
|
(根 / "docs" / "噪音" / "跑痕.json").write_text("{}\n", encoding="utf-8")
|
||||||
|
|
||||||
|
写入目录(根)
|
||||||
|
文本 = (根 / "docs" / "目录.md").read_text(encoding="utf-8")
|
||||||
|
assert (根 / "docs" / "噪音" / "跑痕.json").is_file() # 痕迹在磁盘上,只是不参与派生
|
||||||
|
assert "真实.md" in 文本
|
||||||
|
assert "噪音" not in 文本
|
||||||
|
assert 检查派生目录(根) == []
|
||||||
|
|
||||||
|
|
||||||
def _角色字节(身份: str, 名称: str, 额外: str = "") -> bytes:
|
def _角色字节(身份: str, 名称: str, 额外: str = "") -> bytes:
|
||||||
return (
|
return (
|
||||||
f"---\nid: {身份}\nname: {名称}\ncategory: role\n"
|
f"---\nid: {身份}\nname: {名称}\ncategory: role\n"
|
||||||
|
|||||||
@ -255,3 +255,40 @@ def test_zero_exit_without_execution_evidence_fails_closed__50f80b(项目: 临
|
|||||||
码2, 结果2 = 项目.运行("test_跳过.py")
|
码2, 结果2 = 项目.运行("test_跳过.py")
|
||||||
assert 码2 == 5, (码2, 结果2["stdout"])
|
assert 码2 == 5, (码2, 结果2["stdout"])
|
||||||
assert "零实际执行" in 结果2["stderr"]
|
assert "零实际执行" in 结果2["stderr"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.case_id(
|
||||||
|
"TC-3c9a17d84e60",
|
||||||
|
environment="离线,受限子进程和临时测试项目",
|
||||||
|
when="收集并执行选定用例,模拟超时、缺依赖或零执行。",
|
||||||
|
contract=".agent/rules/测试隔离.md",
|
||||||
|
)
|
||||||
|
def test_独占锁内启动的收集会话不自锁__3c9a17(项目: 临时项目, tmp_path: Path) -> None:
|
||||||
|
"""`验证锁 --执行` 已持独占锁时,子 pytest 会话不再取共享锁(否则自锁)。"""
|
||||||
|
项目.写("tests/单元/test_样本.py", "def test_样本__c10001():\n assert True\n")
|
||||||
|
环境变量 = {**_清理环境(), "MUSE_验证锁": str(tmp_path / "验证锁.lock")}
|
||||||
|
工具目录 = str(项目.根 / "工具")
|
||||||
|
环境变量["PYTHONPATH"] = 工具目录 + (
|
||||||
|
(":" + 环境变量["PYTHONPATH"]) if 环境变量.get("PYTHONPATH") else ""
|
||||||
|
)
|
||||||
|
进程 = subprocess.run(
|
||||||
|
[
|
||||||
|
sys.executable,
|
||||||
|
str(项目.根 / "工具/验证锁.py"),
|
||||||
|
"--执行",
|
||||||
|
sys.executable,
|
||||||
|
"-m",
|
||||||
|
"pytest",
|
||||||
|
"--collect-only",
|
||||||
|
"-q",
|
||||||
|
"-p",
|
||||||
|
"no:cacheprovider",
|
||||||
|
],
|
||||||
|
cwd=项目.根,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=120,
|
||||||
|
env=环境变量,
|
||||||
|
)
|
||||||
|
assert 进程.returncode == 0, (进程.stdout, 进程.stderr)
|
||||||
|
assert "test_样本" in 进程.stdout
|
||||||
|
|||||||
@ -30238,6 +30238,28 @@
|
|||||||
],
|
],
|
||||||
"markers": []
|
"markers": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"case_id": "TC-3c9a17d84e60",
|
||||||
|
"environment": "离线,受限子进程和临时测试项目",
|
||||||
|
"when": "收集并执行选定用例,模拟超时、缺依赖或零执行。",
|
||||||
|
"contract": ".agent/rules/测试隔离.md",
|
||||||
|
"file": "tests/架构/test_测试依赖与执行证据.py",
|
||||||
|
"symbol": "test_独占锁内启动的收集会话不自锁__3c9a17",
|
||||||
|
"parameter_ids": [],
|
||||||
|
"node_ids": [
|
||||||
|
"tests/架构/test_测试依赖与执行证据.py::test_独占锁内启动的收集会话不自锁__3c9a17"
|
||||||
|
],
|
||||||
|
"fixtures": [
|
||||||
|
"request",
|
||||||
|
"tmp_path",
|
||||||
|
"tmp_path_factory",
|
||||||
|
"测试资源接缝",
|
||||||
|
"源码资源",
|
||||||
|
"离线防护",
|
||||||
|
"项目"
|
||||||
|
],
|
||||||
|
"markers": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"case_id": "TC-3d92e0528232",
|
"case_id": "TC-3d92e0528232",
|
||||||
"environment": "默认离线",
|
"environment": "默认离线",
|
||||||
@ -32415,6 +32437,27 @@
|
|||||||
],
|
],
|
||||||
"markers": []
|
"markers": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"case_id": "TC-7b1e4d9c35a2",
|
||||||
|
"environment": "离线,临时 git 工作树",
|
||||||
|
"when": "读取或生成登记资源及导航。",
|
||||||
|
"contract": "docs/系统架构/新版设计/编排与能力资源.md",
|
||||||
|
"file": "tests/架构/test_技能与文档索引.py",
|
||||||
|
"symbol": "test_索引只覆盖会提交内容而忽略运行痕迹__7b1e4d",
|
||||||
|
"parameter_ids": [],
|
||||||
|
"node_ids": [
|
||||||
|
"tests/架构/test_技能与文档索引.py::test_索引只覆盖会提交内容而忽略运行痕迹__7b1e4d"
|
||||||
|
],
|
||||||
|
"fixtures": [
|
||||||
|
"request",
|
||||||
|
"tmp_path",
|
||||||
|
"tmp_path_factory",
|
||||||
|
"测试资源接缝",
|
||||||
|
"源码资源",
|
||||||
|
"离线防护"
|
||||||
|
],
|
||||||
|
"markers": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"case_id": "TC-7c28d3e5268e",
|
"case_id": "TC-7c28d3e5268e",
|
||||||
"environment": "默认离线",
|
"environment": "默认离线",
|
||||||
|
|||||||
109
工具/维护索引.py
109
工具/维护索引.py
@ -37,6 +37,8 @@ _忽略目录片段 = frozenset(
|
|||||||
# 新版用例目录:旧测试树(tests/skills、tests/adapters、tests/architecture 英文目录等)
|
# 新版用例目录:旧测试树(tests/skills、tests/adapters、tests/architecture 英文目录等)
|
||||||
# 由旧环境运行,不进入新版用例身份检查
|
# 由旧环境运行,不进入新版用例身份检查
|
||||||
_新版用例目录 = ("单元", "契约", "集成", "架构", "迁移", "端到端", "真实调用")
|
_新版用例目录 = ("单元", "契约", "集成", "架构", "迁移", "端到端", "真实调用")
|
||||||
|
# 按 git 工作树顶层缓存「会提交内容」,同一轮多次遍历不重复询问 git。
|
||||||
|
_仓库缓存: dict[Path, frozenset[Path] | None] = {}
|
||||||
|
|
||||||
|
|
||||||
def _在忽略目录(路径: Path, 根: Path) -> bool:
|
def _在忽略目录(路径: Path, 根: Path) -> bool:
|
||||||
@ -47,8 +49,90 @@ def _在忽略目录(路径: Path, 根: Path) -> bool:
|
|||||||
return any(片段 in 相对.parts for 片段 in _忽略目录片段)
|
return any(片段 in 相对.parts for 片段 in _忽略目录片段)
|
||||||
|
|
||||||
|
|
||||||
|
def _仓库顶层(根: Path) -> Path | None:
|
||||||
|
"""根所在 git 工作树的顶层目录;不在工作树内时返回 None。"""
|
||||||
|
try:
|
||||||
|
输出 = subprocess.run(
|
||||||
|
["git", "-C", str(根), "rev-parse", "--show-toplevel"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=30,
|
||||||
|
check=True,
|
||||||
|
).stdout.strip()
|
||||||
|
except (OSError, subprocess.SubprocessError):
|
||||||
|
return None
|
||||||
|
return Path(输出).resolve() if 输出 else None
|
||||||
|
|
||||||
|
|
||||||
|
def _会提交内容(根: Path) -> frozenset[Path] | None:
|
||||||
|
"""根下会提交的路径(文件与其祖先目录);非 git 场景返回 None,调用方退回磁盘枚举。
|
||||||
|
|
||||||
|
索引只维护会提交的内容:被 .gitignore 忽略的运行痕迹、检查报告与缓存既不
|
||||||
|
进入派生,也不承担扫描成本。
|
||||||
|
"""
|
||||||
|
顶层 = _仓库顶层(根)
|
||||||
|
if 顶层 is None:
|
||||||
|
return None
|
||||||
|
if 顶层 not in _仓库缓存:
|
||||||
|
try:
|
||||||
|
输出 = subprocess.run(
|
||||||
|
[
|
||||||
|
"git",
|
||||||
|
"-C",
|
||||||
|
str(顶层),
|
||||||
|
"ls-files",
|
||||||
|
"-z",
|
||||||
|
"--cached",
|
||||||
|
"--others",
|
||||||
|
"--exclude-standard",
|
||||||
|
],
|
||||||
|
capture_output=True,
|
||||||
|
timeout=60,
|
||||||
|
check=True,
|
||||||
|
).stdout
|
||||||
|
except (OSError, subprocess.SubprocessError):
|
||||||
|
_仓库缓存[顶层] = None
|
||||||
|
return None
|
||||||
|
文件集: set[Path] = set()
|
||||||
|
for 名 in 输出.decode("utf-8", "surrogateescape").split("\0"):
|
||||||
|
路径 = 顶层 / 名
|
||||||
|
if 名 and 路径.is_file():
|
||||||
|
文件集.add(路径)
|
||||||
|
目录集 = {
|
||||||
|
祖先 for p in 文件集 for 祖先 in p.parents if 祖先 == 顶层 or 顶层 in 祖先.parents
|
||||||
|
}
|
||||||
|
_仓库缓存[顶层] = frozenset(文件集 | 目录集)
|
||||||
|
内容 = _仓库缓存[顶层]
|
||||||
|
if 内容 is None:
|
||||||
|
return None
|
||||||
|
本根 = 根.resolve()
|
||||||
|
return frozenset(p for p in 内容 if p == 本根 or 本根 in p.parents)
|
||||||
|
|
||||||
|
|
||||||
|
def _会提交条目(根: Path, 目录: Path) -> list[Path] | None:
|
||||||
|
"""目录下会提交的一级条目(文件与子目录);非 git 场景返回 None。"""
|
||||||
|
内容 = _会提交内容(根)
|
||||||
|
if 内容 is None:
|
||||||
|
return None
|
||||||
|
return sorted(p for p in 内容 if p.parent == 目录)
|
||||||
|
|
||||||
|
|
||||||
|
def _会提交子目录(根: Path, 目录: Path) -> list[Path]:
|
||||||
|
"""目录下会提交的一级子目录;非 git 场景退回磁盘枚举。"""
|
||||||
|
条目 = _会提交条目(根, 目录)
|
||||||
|
if 条目 is None:
|
||||||
|
return sorted(p for p in 目录.iterdir() if p.is_dir())
|
||||||
|
return [p for p in 条目 if p.is_dir()]
|
||||||
|
|
||||||
|
|
||||||
def 遍历文件(根: Path, 模式: str):
|
def 遍历文件(根: Path, 模式: str):
|
||||||
"""在进入目录前剪枝,忽略缓存和历史树不承担扫描成本。"""
|
"""只遍历会提交的内容;忽略的运行痕迹不参与派生,也不承担扫描成本。"""
|
||||||
|
内容 = _会提交内容(根)
|
||||||
|
if 内容 is not None:
|
||||||
|
for 文件 in sorted(内容):
|
||||||
|
if 文件.is_file() and fnmatch(文件.name, 模式) and not _在忽略目录(文件, 根):
|
||||||
|
yield 文件
|
||||||
|
return
|
||||||
for 当前, 目录名, 文件名 in os.walk(根, followlinks=False):
|
for 当前, 目录名, 文件名 in os.walk(根, followlinks=False):
|
||||||
目录名[:] = sorted(名 for 名 in 目录名 if 名 not in _忽略目录片段)
|
目录名[:] = sorted(名 for 名 in 目录名 if 名 not in _忽略目录片段)
|
||||||
for 名 in sorted(文件名):
|
for 名 in sorted(文件名):
|
||||||
@ -102,8 +186,8 @@ def 检查技能目录(根: Path) -> list[str]:
|
|||||||
return ["技能目录不存在:.agent/skills(缺失即失败,不静默跳过)"]
|
return ["技能目录不存在:.agent/skills(缺失即失败,不静默跳过)"]
|
||||||
问题: list[str] = []
|
问题: list[str] = []
|
||||||
已见: dict[str, str] = {}
|
已见: dict[str, str] = {}
|
||||||
for 分组 in sorted(p for p in 技能根.iterdir() if p.is_dir()):
|
for 分组 in _会提交子目录(根, 技能根):
|
||||||
for 技能目录 in sorted(p for p in 分组.iterdir() if p.is_dir()):
|
for 技能目录 in _会提交子目录(根, 分组):
|
||||||
名称 = 技能目录.name
|
名称 = 技能目录.name
|
||||||
相对 = str(技能目录.relative_to(根))
|
相对 = str(技能目录.relative_to(根))
|
||||||
技能文件 = 技能目录 / "SKILL.md"
|
技能文件 = 技能目录 / "SKILL.md"
|
||||||
@ -189,7 +273,7 @@ def _目录文件(根: Path) -> list[Path]:
|
|||||||
}
|
}
|
||||||
技能根 = 根 / ".agent/skills"
|
技能根 = 根 / ".agent/skills"
|
||||||
if 技能根.is_dir():
|
if 技能根.is_dir():
|
||||||
文件组.update(p / "目录.md" for p in 技能根.iterdir() if p.is_dir())
|
文件组.update(p / "目录.md" for p in _会提交子目录(根, 技能根))
|
||||||
return sorted(文件组)
|
return sorted(文件组)
|
||||||
|
|
||||||
|
|
||||||
@ -251,16 +335,15 @@ def 派生目录(根: Path) -> dict[Path, str]:
|
|||||||
原文 = 索引.read_text(encoding="utf-8") if 索引.is_file() else ""
|
原文 = 索引.read_text(encoding="utf-8") if 索引.is_file() else ""
|
||||||
旧行 = _表格行(原文)
|
旧行 = _表格行(原文)
|
||||||
分组 = 索引.parent.parent == 技能根
|
分组 = 索引.parent.parent == 技能根
|
||||||
|
条目 = _会提交条目(根, 索引.parent)
|
||||||
|
if 条目 is None:
|
||||||
|
条目 = sorted(索引.parent.iterdir())
|
||||||
if 分组:
|
if 分组:
|
||||||
文件组 = [
|
文件组 = [p / "SKILL.md" for p in 条目 if p.is_dir() and (p / "SKILL.md").is_file()]
|
||||||
p / "SKILL.md"
|
|
||||||
for p in sorted(索引.parent.iterdir())
|
|
||||||
if p.is_dir() and (p / "SKILL.md").is_file()
|
|
||||||
]
|
|
||||||
else:
|
else:
|
||||||
文件组 = [
|
文件组 = [
|
||||||
(p / "目录.md" if (p / "目录.md").is_file() else p)
|
(p / "目录.md" if (p / "目录.md").is_file() else p)
|
||||||
for p in sorted(索引.parent.iterdir())
|
for p in 条目
|
||||||
if p != 索引 and not p.name.startswith(".") and p.name != "__pycache__"
|
if p != 索引 and not p.name.startswith(".") and p.name != "__pycache__"
|
||||||
]
|
]
|
||||||
新行 = {}
|
新行 = {}
|
||||||
@ -352,7 +435,7 @@ def 检查技能索引(根: Path) -> list[str]:
|
|||||||
if not 技能根.is_dir():
|
if not 技能根.is_dir():
|
||||||
return ["技能目录不存在:.agent/skills"]
|
return ["技能目录不存在:.agent/skills"]
|
||||||
问题 = []
|
问题 = []
|
||||||
for 分组 in sorted(p for p in 技能根.iterdir() if p.is_dir()):
|
for 分组 in _会提交子目录(根, 技能根):
|
||||||
索引 = 分组 / "目录.md"
|
索引 = 分组 / "目录.md"
|
||||||
if not 索引.is_file():
|
if not 索引.is_file():
|
||||||
问题.append(f"分组缺目录:{分组.name}")
|
问题.append(f"分组缺目录:{分组.name}")
|
||||||
@ -363,9 +446,7 @@ def 检查技能索引(根: Path) -> list[str]:
|
|||||||
if 地址.endswith("/SKILL.md")
|
if 地址.endswith("/SKILL.md")
|
||||||
]
|
]
|
||||||
磁盘名 = {
|
磁盘名 = {
|
||||||
f"{p.name}/SKILL.md"
|
f"{p.name}/SKILL.md" for p in _会提交子目录(根, 分组) if (p / "SKILL.md").is_file()
|
||||||
for p in 分组.iterdir()
|
|
||||||
if p.is_dir() and (p / "SKILL.md").is_file()
|
|
||||||
}
|
}
|
||||||
if set(索引名) != 磁盘名:
|
if set(索引名) != 磁盘名:
|
||||||
问题.append(
|
问题.append(
|
||||||
|
|||||||
@ -25,6 +25,12 @@ from typing import Any
|
|||||||
_持锁标记 = "MUSE_写入锁已持有"
|
_持锁标记 = "MUSE_写入锁已持有"
|
||||||
|
|
||||||
|
|
||||||
|
def 已在独占锁内() -> bool:
|
||||||
|
"""本进程是否由 `--执行` 启动:父进程已持独占写锁,不能再取共享锁。"""
|
||||||
|
|
||||||
|
return os.environ.get(_持锁标记) == "1"
|
||||||
|
|
||||||
|
|
||||||
def 正在验证() -> str | None:
|
def 正在验证() -> str | None:
|
||||||
"""返回当前验证说明;没有验证在跑时返回 None。
|
"""返回当前验证说明;没有验证在跑时返回 None。
|
||||||
|
|
||||||
@ -32,7 +38,7 @@ def 正在验证() -> str | None:
|
|||||||
不能把自己判成「验证正在运行」;用环境变量把这一事实传给子进程。
|
不能把自己判成「验证正在运行」;用环境变量把这一事实传给子进程。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if os.environ.get(_持锁标记) == "1":
|
if 已在独占锁内():
|
||||||
return None
|
return None
|
||||||
if not 锁路径.exists():
|
if not 锁路径.exists():
|
||||||
return None
|
return None
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user