wip: local BUG-139/BUG-140 fixes before syncing origin/main
- pre_work_check.py: prefer repo .venv Python >=3.11, strict JYOTISH_PRE_WORK_PYTHON override, fail closed - tests/test_pre_work_check.py: regression coverage for venv preference / system 3.9 / strict override - globals.css: transient :active pressed feedback for entrypoint cards, hover only under (hover: hover) - consultation-entrypoint.test.ts: sticky-hover regression test - BUG_HISTORY.md: BUG-139 / BUG-140 records - pre_work_error_ledger.md: ERR-078 resolved entry
This commit is contained in:
@@ -2384,3 +2384,35 @@
|
||||
- 相关记录:BUG-134、BUG-137、ERR-097、ERR-100、ERR-101
|
||||
- 复发自:无
|
||||
- 修复版本:`6c1dcbe857006ec6ae7463b57b2b7d5947da4851`
|
||||
|
||||
## BUG-139 | 系统 Python 启动预检时重复使用不兼容解释器
|
||||
|
||||
- 状态:resolved
|
||||
- 首次发现:2026-08-07
|
||||
- 最近更新:2026-08-07
|
||||
- 影响面:`scripts/pre_work_check.py`、开工预检、碎片扫描与聚焦治理测试
|
||||
- 用户现象:每次执行文档规定的 `python3 scripts/pre_work_check.py ...`,碎片扫描都会因 Python 3.9 不支持项目使用的 PEP 604 类型注解而退出,聚焦测试同时报 `/usr/bin/python3: No module named pytest`。
|
||||
- 触发条件:macOS 的裸 `python3` 指向 `/usr/bin/python3` 3.9,而仓库 `.venv` 已安装 Python 3.11 与 pytest。
|
||||
- 根因:预检脚本把启动自身的 `sys.executable` 固定为全部子命令的解释器,没有验证项目声明的 Python >=3.11,也没有优先使用仓库虚拟环境;`ERR-078` 原防线只要求操作者手动改用 `.venv`,因此文档中的标准命令仍会稳定复发。
|
||||
- 修复:预检入口按确定顺序探测项目 `.venv`、当前解释器和可用的 Python 3.11+;只有同时满足 Python >=3.11 与 pytest 可用才执行全部 Python 子检查。报告显式记录选择结果和各候选探测信息;找不到兼容环境时失败闭合并给出安装指引。新增严格的 `JYOTISH_PRE_WORK_PYTHON` 覆盖入口,配置错误时不静默退回其他解释器。
|
||||
- 验证:`tests/test_pre_work_check.py` 覆盖虚拟环境优先、系统 3.9 跳过、严格 override 和 runtime failure 状态;使用裸 `/usr/bin/python3 scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45` 完整通过,报告选择仓库 `.venv/bin/python` 3.11 且 focused governance tests 通过。
|
||||
- 防复发:预检不得再直接用未经探测的 `sys.executable` 启动项目脚本或 pytest;标准文档命令必须纳入真实系统 Python 启动回归,且 JSON 报告必须保留 `python_runtime_ok` 和实际解释器信息。
|
||||
- 相关记录:ERR-011、ERR-014、ERR-078
|
||||
- 复发自:无
|
||||
- 修复版本:待提交
|
||||
|
||||
## BUG-140 | 首页入口卡片点击后持续显示灰色交互态
|
||||
|
||||
- 状态:resolved
|
||||
- 首次发现:2026-08-07
|
||||
- 最近更新:2026-08-07
|
||||
- 影响面:首页 `starter-theme-card`、`daily-starlanguage-card` 与共享 `product-entrypoint-card` 交互反馈
|
||||
- 用户现象:用户点击首页主题或产品入口卡片后,卡片背景持续处于灰色/选中样式,没有在松开指针后恢复。
|
||||
- 触发条件:在触摸设备点击卡片,或在桌面端点击后让指针继续停留在卡片上。
|
||||
- 根因:卡片把铺满表面的背景反馈绑定到无设备能力边界的 `:hover`;移动浏览器可能在点击后保留模拟 hover,桌面指针停留也会让一次点击看起来像永久选中。`hover`、短暂按压和真实 disabled 三种状态因此在视觉上混在一起。
|
||||
- 修复:将铺灰背景收敛到 `:active`,只在按压期间显示并于释放后恢复;桌面 `@media (hover: hover)` 只保留轻量边框、光泽和箭头位移,不再改变卡片底色;键盘继续使用现有 `focus-visible` 轮廓,真实 disabled 透明度规则保持不变。
|
||||
- 验证:`frontend/tests/consultation-entrypoint.test.ts` 新增 sticky-hover 回归;与 `frontend/tests/starter-questions.test.ts` 精确运行 40/40 通过;目标 ESLint 与 `git diff --check` 通过。全量前端尝试中 1457 项通过,16 项因本机缺少 Docker 或 `python` 可执行命令而失败,与本次 CSS 修改无关。
|
||||
- 防复发:首页可点击卡片的表面底色只能由短暂 `:active` 或真实业务 disabled 状态改变;hover 视觉必须限制在支持 hover 的设备,且回归测试禁止重新为这些卡片的 hover surface 添加背景色。
|
||||
- 相关记录:无
|
||||
- 复发自:无
|
||||
- 修复版本:待提交
|
||||
|
||||
@@ -108,7 +108,7 @@ For large architecture or release work, also read:
|
||||
| ERR-075 | `real_case_calibration_catalog()` unconditionally opened an untracked holdout manifest, so clean installs could silently lose the MCP runtime evidence log. | resolved 2026-07-16 | Missing optional holdout assets return structured `blocked` metadata instead of raising. |
|
||||
| ERR-076 | Fragment/inventory tests required machine-local drafts, workspace residue, or a fixed minimum file count. | resolved 2026-07-16 | Assert count invariants and stable tracked classifications; validate optional local categories only when present. |
|
||||
| ERR-077 | VedAstro request previews serialized the live `x-api-key` header, allowing logs and failed test output to expose credentials. | resolved; credential rotation required 2026-07-16 | Preview/metadata profiles never contain authentication headers. Inject `VEDASTRO_API_KEY` only immediately before network execution; rotate any key exposed before this fix. |
|
||||
| ERR-078 | On this macOS host, bare `python3` resolves to Python 3.9 without pytest, while the project uses PEP 604 annotations; rerunning with `.venv/bin/python` reaches the tests but `test_preflight_fragment_scan_reports_authority_layers_and_risk_buckets` has a stale `high_value_unpromoted_count >= 1` assertion even when the scanner correctly reports zero. | observed 2026-07-15 | Run the gate with the project venv on this host and do not claim the full pre-work gate passed until the fragment-governance assertion is reconciled with the valid zero-unpromoted state. |
|
||||
| ERR-078 | On this macOS host, bare `python3` resolves to Python 3.9 without pytest, while the project requires Python >=3.11 and uses PEP 604 annotations. The pre-work launcher previously reused that incompatible interpreter for every child check. | resolved 2026-08-07 | `pre_work_check.py` must probe and prefer the repository `.venv` (Python >=3.11 with pytest), expose the selected runtime in its JSON report, and fail closed with setup guidance if no compatible runtime exists. Keep regression coverage for system-Python launch and strict `JYOTISH_PRE_WORK_PYTHON` override behavior. |
|
||||
| ERR-079 | VedAstro hosted replay and fixed-commit self-host validation cannot run on this host when `VEDASTRO_API_ENDPOINT` / `VEDASTRO_API_KEY` are absent and neither a .NET runtime nor a reachable Docker daemon is available. | observed 2026-07-17 | Keep hosted closure `blocked`; run `vedastro_contract_probe.py --repeats 3` only after injecting credentials through the environment, and build the pinned source in a provisioned Windows/.NET or reachable container environment. Never persist credentials in artifacts or shell history. |
|
||||
|
||||
## ERR-080 | Issue link was mistaken for benchmark provenance | mitigated 2026-07-18
|
||||
|
||||
@@ -550,9 +550,11 @@ button:disabled { cursor: default; opacity: .45; }
|
||||
.product-entrypoint-card { position: relative; transition: border-color 120ms ease-out, background-color 120ms ease-out, transform 120ms ease-out; }
|
||||
.product-entrypoint-hitarea { position: absolute; z-index: 2; inset: 0; width: 100%; min-height: 0; padding: 0; border: 0; border-radius: inherit; background: transparent; cursor: pointer; }
|
||||
.product-entrypoint-card > :not(.product-entrypoint-hitarea) { position: relative; z-index: 1; pointer-events: none; }
|
||||
.product-entrypoint-card:has(.product-entrypoint-hitarea:not(:disabled):hover) { border-color: color-mix(in srgb, var(--color-action) 44%, var(--color-border)); background: var(--color-action-soft); transform: translateY(-1px); }
|
||||
.product-entrypoint-card:has(.product-entrypoint-hitarea:not(:disabled):active) { transform: translateY(0); }
|
||||
.product-entrypoint-card:has(.product-entrypoint-hitarea:not(:disabled):active) { border-color: color-mix(in srgb, var(--color-action) 44%, var(--color-border)); background: var(--color-action-soft); transform: translateY(0); }
|
||||
.product-entrypoint-card:has(.product-entrypoint-hitarea:focus-visible) { outline: 3px solid color-mix(in srgb, var(--color-focus) 56%, transparent); outline-offset: 2px; }
|
||||
@media (hover: hover) {
|
||||
.product-entrypoint-card:has(.product-entrypoint-hitarea:not(:disabled):hover) { border-color: color-mix(in srgb, var(--color-action) 44%, var(--color-border)); transform: translateY(-1px); }
|
||||
}
|
||||
.product-entrypoint-card:has(.product-entrypoint-hitarea:disabled) { opacity: .65; }
|
||||
.product-entrypoint-hitarea:disabled { cursor: not-allowed; }
|
||||
.daily-starlanguage-heading { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); }
|
||||
@@ -1119,19 +1121,25 @@ input:disabled, select:disabled { color: var(--color-ink-tertiary); background:
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.product-entrypoint-card:has(.product-entrypoint-hitarea:not(:disabled):hover) {
|
||||
.product-entrypoint-card:has(.product-entrypoint-hitarea:not(:disabled):active) {
|
||||
border-color: transparent;
|
||||
background: color-mix(in srgb, var(--color-action-soft) 58%, var(--color-canvas));
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.product-entrypoint-card:has(.product-entrypoint-hitarea:not(:disabled):hover)::after {
|
||||
opacity: .65;
|
||||
transform: translateX(48%);
|
||||
}
|
||||
@media (hover: hover) {
|
||||
.product-entrypoint-card:has(.product-entrypoint-hitarea:not(:disabled):hover) {
|
||||
border-color: transparent;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.product-entrypoint-card:has(.product-entrypoint-hitarea:not(:disabled):hover) .product-entrypoint-action {
|
||||
transform: translateX(3px);
|
||||
.product-entrypoint-card:has(.product-entrypoint-hitarea:not(:disabled):hover)::after {
|
||||
opacity: .65;
|
||||
transform: translateX(48%);
|
||||
}
|
||||
|
||||
.product-entrypoint-card:has(.product-entrypoint-hitarea:not(:disabled):hover) .product-entrypoint-action {
|
||||
transform: translateX(3px);
|
||||
}
|
||||
}
|
||||
|
||||
.product-entrypoint-card:has(.product-entrypoint-hitarea:focus-visible) {
|
||||
@@ -1270,17 +1278,22 @@ input:disabled, select:disabled { color: var(--color-ink-tertiary); background:
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.starter-theme-card:not(:disabled):hover,
|
||||
.starter-theme-card:focus-visible {
|
||||
.starter-theme-card:not(:disabled):active {
|
||||
background: color-mix(in srgb, var(--color-action-soft) 56%, var(--color-canvas));
|
||||
}
|
||||
|
||||
.starter-theme-card:not(:disabled):hover .starter-arrow,
|
||||
.starter-theme-card:focus-visible .starter-arrow {
|
||||
.starter-theme-card:not(:disabled):active .starter-arrow {
|
||||
color: var(--color-action);
|
||||
transform: translate(2px, -2px);
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.starter-theme-card:not(:disabled):hover .starter-arrow {
|
||||
color: var(--color-action);
|
||||
transform: translate(2px, -2px);
|
||||
}
|
||||
}
|
||||
|
||||
.starter-content {
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
@@ -1438,11 +1451,6 @@ input:disabled, select:disabled { color: var(--color-ink-tertiary); background:
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.starter-theme-card:not(:disabled):hover,
|
||||
.starter-theme-card:focus-visible {
|
||||
background: color-mix(in srgb, var(--color-action-soft) 56%, var(--color-canvas));
|
||||
}
|
||||
|
||||
.starter-content span {
|
||||
max-width: 440px;
|
||||
}
|
||||
|
||||
@@ -289,6 +289,22 @@ test("homepage entrypoints use two whole-card native actions", () => {
|
||||
assert.doesNotMatch(source, /className="daily-starlanguage-heading">[\s\S]{0,180}<button/);
|
||||
});
|
||||
|
||||
test("starter cards use transient pressed feedback instead of sticky hover shading", () => {
|
||||
const styles = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8");
|
||||
const productHoverRules = [...styles.matchAll(
|
||||
/\.product-entrypoint-card:has\(\.product-entrypoint-hitarea:not\(:disabled\):hover\)\s*\{([^}]*)\}/g,
|
||||
)];
|
||||
const starterHoverSurfaceRules = [...styles.matchAll(
|
||||
/\.starter-theme-card:not\(:disabled\):hover\s*\{([^}]*)\}/g,
|
||||
)];
|
||||
|
||||
assert.ok(productHoverRules.length > 0);
|
||||
for (const rule of productHoverRules) assert.doesNotMatch(rule[1], /background\s*:/);
|
||||
assert.equal(starterHoverSurfaceRules.length, 0);
|
||||
assert.match(styles, /\.product-entrypoint-card:has\(\.product-entrypoint-hitarea:not\(:disabled\):active\)\s*\{[^}]*background\s*:/);
|
||||
assert.match(styles, /\.starter-theme-card:not\(:disabled\):active\s*\{[^}]*background\s*:/);
|
||||
});
|
||||
|
||||
test("starter homepage stays editorial and hides technical chart parameters", () => {
|
||||
const source = readFileSync(new URL("../src/app/page.tsx", import.meta.url), "utf8");
|
||||
const styles = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8");
|
||||
|
||||
+122
-7
@@ -11,15 +11,17 @@ from __future__ import annotations
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
PYTHON = sys.executable
|
||||
MINIMUM_PYTHON_VERSION = (3, 11)
|
||||
PYTHON_OVERRIDE_ENV = "JYOTISH_PRE_WORK_PYTHON"
|
||||
DEFAULT_COMMAND_TIMEOUT_SECONDS = 45
|
||||
DEFAULT_FRAGMENT_TIMEOUT_SECONDS = 90
|
||||
FOCUSED_TEST_TARGETS = [
|
||||
@@ -39,6 +41,97 @@ PRE_WORK_DOCS = [
|
||||
]
|
||||
|
||||
|
||||
def python_candidates(
|
||||
*,
|
||||
root: Path = ROOT,
|
||||
environ: dict[str, str] | None = None,
|
||||
current_executable: str | None = None,
|
||||
) -> list[str]:
|
||||
"""Return Python candidates in deterministic preference order.
|
||||
|
||||
An explicit override is strict: a broken override must fail closed rather
|
||||
than silently run the checks under a different environment.
|
||||
"""
|
||||
env = os.environ if environ is None else environ
|
||||
override = env.get(PYTHON_OVERRIDE_ENV, "").strip()
|
||||
if override:
|
||||
return [override]
|
||||
|
||||
candidates = [
|
||||
str(root / ".venv" / "bin" / "python"),
|
||||
str(root / ".venv" / "Scripts" / "python.exe"),
|
||||
current_executable or sys.executable,
|
||||
]
|
||||
candidates.extend(path for name in ("python3.13", "python3.12", "python3.11") if (path := shutil.which(name)))
|
||||
return list(dict.fromkeys(candidate for candidate in candidates if candidate))
|
||||
|
||||
|
||||
def probe_python(candidate: str) -> dict[str, Any]:
|
||||
probe = (
|
||||
"import importlib.util,json,sys;"
|
||||
"print(json.dumps({'version':list(sys.version_info[:3]),"
|
||||
"'pytest_available':importlib.util.find_spec('pytest') is not None}))"
|
||||
)
|
||||
try:
|
||||
completed = subprocess.run(
|
||||
[candidate, "-c", probe],
|
||||
cwd=ROOT,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
timeout=10,
|
||||
check=False,
|
||||
)
|
||||
except (OSError, subprocess.TimeoutExpired) as exc:
|
||||
return {"candidate": candidate, "ok": False, "error": str(exc)}
|
||||
|
||||
if completed.returncode != 0:
|
||||
return {
|
||||
"candidate": candidate,
|
||||
"ok": False,
|
||||
"error": (completed.stderr or completed.stdout).strip() or f"probe exited {completed.returncode}",
|
||||
}
|
||||
try:
|
||||
metadata = json.loads(completed.stdout)
|
||||
version = tuple(int(part) for part in metadata["version"])
|
||||
pytest_available = metadata["pytest_available"] is True
|
||||
except (KeyError, TypeError, ValueError, json.JSONDecodeError) as exc:
|
||||
return {"candidate": candidate, "ok": False, "error": f"invalid probe output: {exc}"}
|
||||
|
||||
requirements = []
|
||||
if version < MINIMUM_PYTHON_VERSION:
|
||||
requirements.append(f"Python {version[0]}.{version[1]} is below required 3.11")
|
||||
if not pytest_available:
|
||||
requirements.append("pytest is not installed")
|
||||
return {
|
||||
"candidate": candidate,
|
||||
"ok": not requirements,
|
||||
"version": ".".join(str(part) for part in version),
|
||||
"pytest_available": pytest_available,
|
||||
"error": "; ".join(requirements),
|
||||
}
|
||||
|
||||
|
||||
def select_python(
|
||||
candidates: list[str] | None = None,
|
||||
probe: Callable[[str], dict[str, Any]] = probe_python,
|
||||
) -> dict[str, Any]:
|
||||
attempts = []
|
||||
for candidate in candidates or python_candidates():
|
||||
result = probe(candidate)
|
||||
attempts.append(result)
|
||||
if result.get("ok"):
|
||||
return {"ok": True, "executable": candidate, "attempts": attempts, **result}
|
||||
return {
|
||||
"ok": False,
|
||||
"executable": "",
|
||||
"attempts": attempts,
|
||||
"error": (
|
||||
"No usable project Python found. Create .venv with Python >=3.11 and install requirements-dev.txt, "
|
||||
f"or set {PYTHON_OVERRIDE_ENV} to a compatible interpreter."
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def run(args: list[str], timeout: int, env: dict[str, str] | None = None) -> dict[str, Any]:
|
||||
try:
|
||||
completed = subprocess.run(
|
||||
@@ -73,8 +166,9 @@ def classify_status(
|
||||
pytest_ok: bool,
|
||||
remote_status: str,
|
||||
external_engine_ok: bool = True,
|
||||
python_ok: bool = True,
|
||||
) -> str:
|
||||
if not docs_ok or not fragment_ok or not pytest_ok or not external_engine_ok:
|
||||
if not docs_ok or not fragment_ok or not pytest_ok or not external_engine_ok or not python_ok:
|
||||
return "fail"
|
||||
if remote_status == "verified":
|
||||
return "pass"
|
||||
@@ -85,9 +179,23 @@ def build_report(remote_timeout: int, command_timeout: int, fragment_timeout: in
|
||||
docs = {path: (ROOT / path).exists() for path in PRE_WORK_DOCS}
|
||||
git_status = run(["git", "status", "--short", "--branch"], command_timeout)
|
||||
git_remote = run(["git", "remote", "-v"], command_timeout)
|
||||
fragment = run([PYTHON, "scripts/preflight_fragment_scan.py"], fragment_timeout)
|
||||
external_engine = run([PYTHON, EXTERNAL_ENGINE_DIAGNOSTIC_TARGET, "--json"], command_timeout)
|
||||
remote = run([PYTHON, "scripts/remote_repo_visibility_check.py", "--timeout", str(remote_timeout)], command_timeout)
|
||||
python_runtime = select_python()
|
||||
python = str(python_runtime.get("executable") or "")
|
||||
if python_runtime["ok"]:
|
||||
fragment = run([python, "scripts/preflight_fragment_scan.py"], fragment_timeout)
|
||||
external_engine = run([python, EXTERNAL_ENGINE_DIAGNOSTIC_TARGET, "--json"], command_timeout)
|
||||
remote = run([python, "scripts/remote_repo_visibility_check.py", "--timeout", str(remote_timeout)], command_timeout)
|
||||
else:
|
||||
unavailable = {
|
||||
"ok": False,
|
||||
"returncode": None,
|
||||
"stdout": "",
|
||||
"stderr": "",
|
||||
"error": python_runtime["error"],
|
||||
}
|
||||
fragment = dict(unavailable)
|
||||
external_engine = dict(unavailable)
|
||||
remote = dict(unavailable)
|
||||
remote_report: dict[str, Any] = {}
|
||||
if remote["ok"]:
|
||||
try:
|
||||
@@ -101,7 +209,10 @@ def build_report(remote_timeout: int, command_timeout: int, fragment_timeout: in
|
||||
cache = Path(tempfile.gettempdir()) / "jyotish_preflight_fragment_scan_report.json"
|
||||
cache.write_text(fragment["stdout"], encoding="utf-8")
|
||||
env["PREFLIGHT_FRAGMENT_SCAN_REPORT"] = str(cache)
|
||||
pytest_result = run([PYTHON, "-m", "pytest", "-q", *FOCUSED_TEST_TARGETS], command_timeout, env=env)
|
||||
if python_runtime["ok"]:
|
||||
pytest_result = run([python, "-m", "pytest", "-q", *FOCUSED_TEST_TARGETS], command_timeout, env=env)
|
||||
else:
|
||||
pytest_result = {"ok": False, "stdout": "", "stderr": "", "error": python_runtime["error"]}
|
||||
remote_status = str(remote_report.get("status") or "blocked")
|
||||
status = classify_status(
|
||||
docs_ok=all(docs.values()),
|
||||
@@ -109,11 +220,13 @@ def build_report(remote_timeout: int, command_timeout: int, fragment_timeout: in
|
||||
pytest_ok=pytest_result["ok"],
|
||||
remote_status=remote_status,
|
||||
external_engine_ok=external_engine["ok"],
|
||||
python_ok=python_runtime["ok"],
|
||||
)
|
||||
return {
|
||||
"scope": "pre_work_check",
|
||||
"status": status,
|
||||
"must_not_claim_synced": remote_report.get("must_not_claim_synced", True),
|
||||
"python": python_runtime,
|
||||
"docs": docs,
|
||||
"git": {
|
||||
"status_ok": git_status["ok"],
|
||||
@@ -122,6 +235,7 @@ def build_report(remote_timeout: int, command_timeout: int, fragment_timeout: in
|
||||
"remote": git_remote["stdout"],
|
||||
},
|
||||
"checks": {
|
||||
"python_runtime_ok": python_runtime["ok"],
|
||||
"fragment_scan_ok": fragment["ok"],
|
||||
"external_engine_adapters_ok": external_engine["ok"],
|
||||
"remote_visibility_status": remote_status,
|
||||
@@ -129,6 +243,7 @@ def build_report(remote_timeout: int, command_timeout: int, fragment_timeout: in
|
||||
"focused_tests_ok": pytest_result["ok"],
|
||||
},
|
||||
"errors": {
|
||||
"python_runtime": python_runtime.get("error", ""),
|
||||
"fragment_scan": fragment["error"],
|
||||
"external_engine_adapters": external_engine["error"],
|
||||
"remote_visibility": remote["error"],
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from scripts.pre_work_check import (
|
||||
DEFAULT_COMMAND_TIMEOUT_SECONDS,
|
||||
DEFAULT_FRAGMENT_TIMEOUT_SECONDS,
|
||||
EXTERNAL_ENGINE_DIAGNOSTIC_TARGET,
|
||||
FOCUSED_TEST_TARGETS,
|
||||
PRE_WORK_DOCS,
|
||||
PYTHON_OVERRIDE_ENV,
|
||||
classify_status,
|
||||
python_candidates,
|
||||
select_python,
|
||||
)
|
||||
|
||||
|
||||
@@ -14,6 +19,55 @@ def test_classify_status_keeps_remote_blocked_distinct_from_failure() -> None:
|
||||
assert classify_status(True, True, True, "verified") == "pass"
|
||||
assert classify_status(True, True, True, "blocked") == "pass_with_remote_blocked"
|
||||
assert classify_status(True, True, False, "verified") == "fail"
|
||||
assert classify_status(True, True, True, "verified", python_ok=False) == "fail"
|
||||
|
||||
|
||||
def test_pre_work_check_prefers_project_venv_over_launching_python(tmp_path: Path) -> None:
|
||||
candidates = python_candidates(root=tmp_path, environ={}, current_executable="/usr/bin/python3")
|
||||
|
||||
assert candidates[0] == str(tmp_path / ".venv" / "bin" / "python")
|
||||
assert candidates[1] == str(tmp_path / ".venv" / "Scripts" / "python.exe")
|
||||
assert candidates[2] == "/usr/bin/python3"
|
||||
|
||||
|
||||
def test_pre_work_check_python_override_is_strict() -> None:
|
||||
candidates = python_candidates(
|
||||
environ={PYTHON_OVERRIDE_ENV: "/opt/project-python"},
|
||||
current_executable="/usr/bin/python3",
|
||||
)
|
||||
|
||||
assert candidates == ["/opt/project-python"]
|
||||
|
||||
|
||||
def test_pre_work_check_skips_incompatible_launcher_and_selects_venv() -> None:
|
||||
results = {
|
||||
"/usr/bin/python3": {
|
||||
"candidate": "/usr/bin/python3",
|
||||
"ok": False,
|
||||
"version": "3.9.6",
|
||||
"pytest_available": False,
|
||||
"error": "Python 3.9 is below required 3.11; pytest is not installed",
|
||||
},
|
||||
".venv/bin/python": {
|
||||
"candidate": ".venv/bin/python",
|
||||
"ok": True,
|
||||
"version": "3.11.15",
|
||||
"pytest_available": True,
|
||||
"error": "",
|
||||
},
|
||||
}
|
||||
|
||||
selected = select_python(
|
||||
["/usr/bin/python3", ".venv/bin/python"],
|
||||
probe=lambda candidate: results[candidate],
|
||||
)
|
||||
|
||||
assert selected["ok"] is True
|
||||
assert selected["executable"] == ".venv/bin/python"
|
||||
assert [attempt["candidate"] for attempt in selected["attempts"]] == [
|
||||
"/usr/bin/python3",
|
||||
".venv/bin/python",
|
||||
]
|
||||
|
||||
|
||||
def test_pre_work_check_runs_governance_test_set() -> None:
|
||||
|
||||
Reference in New Issue
Block a user