Spaces:
Sleeping
Sleeping
File size: 3,735 Bytes
78738de | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | """Fixtures dùng chung cho test app (ImplPlan Bước T) — 25/07/2026.
Nguyên tắc: test kiểm CONTRACT (luật 9-ball, shape response, message), KHÔNG
kiểm vật lý. pooltool thật nặng (~40s JIT Numba lần đầu) và không cần cho các
gate này → nhét stub vào sys.modules trước khi import bất cứ thứ gì.
"""
from __future__ import annotations
import sys
import types
from pathlib import Path
# Repo KHÔNG cài package: phải tự đẩy src/ vào sys.path TRƯỚC mọi import
# poolcoach_rl. Đừng dựa vào app/main.py (nó cũng chèn src, nhưng chỉ khi
# được import → pass/fail sẽ phụ thuộc thứ tự collect của pytest).
ROOT = Path(__file__).resolve().parents[1]
for _p in (ROOT / "src", ROOT):
if str(_p) not in sys.path:
sys.path.insert(0, str(_p))
import numpy as np # noqa: E402
import pytest # noqa: E402
def _install_pooltool_stub() -> None:
"""Stub `pooltool` + `pooltool.constants` (module rỗng).
`envs/position_env.py` import pooltool ở top-level nhưng mọi chỗ dùng
`pt.` đều nằm trong thân hàm (annotation lazy nhờ `from __future__ import
annotations`) → stub rỗng vẫn import lọt, đủ để `validate_full` lấy
BALL_R. Cần gymnasium THẬT (venv có sẵn).
Cố ý để stub thiếu `System`/`simulate`/`EventType`: nếu test nào lỡ chạm
vào tầng vật lý, nó sẽ ném AttributeError ngay thay vì âm thầm chạy sim.
"""
if "pooltool" in sys.modules:
return
pt = types.ModuleType("pooltool")
constants = types.ModuleType("pooltool.constants")
constants.pocketed = "pocketed"
pt.constants = constants
sys.modules["pooltool"] = pt
sys.modules["pooltool.constants"] = constants
_install_pooltool_stub()
class EnvStub:
"""Env tối thiểu cho tầng luật + describe_full: hằng số bàn thật + Q cố định.
Chỉ cần `w/l/_pockets/_bounds/_position_q` — `judge_shot` gọi
`_position_q`, `pocket_name` đọc `_pockets/w/l`, `validate_full` đọc
`_bounds`.
"""
w = 0.9906
l = 1.9812
# Envelope vị trí nghỉ hợp lệ — SỐ DẪN RA TỪ BÀN THẬT, không phải số chọn
# tay: `table_bounds(w, l, pt.Table.default().pockets.values())` =
# hộp bao của (mặt bàn ∪ 6 đường tròn lỗ). Chép cứng vào đây vì stub không
# có `table`, và conftest cố ý không có pooltool thật để dựng bàn.
#
# ĐỔI SPECS BÀN THÌ PHẢI SINH LẠI 4 SỐ NÀY. `test_validate_full.py` khoá cả
# hai vế: một test kiểm `table_bounds` thật sự DẪN RA hộp từ hình học lỗ
# (nạp bàn giả vào, kiểm mọi đường tròn lỗ nằm trọn trong hộp), một test
# kiểm bộ số dưới đây đúng bằng cái `table_bounds` trả về trên specs bàn
# mặc định của pooltool.
_bounds = (-0.133, 1.1236, -0.09148635277547904, 2.072686352775479)
def __init__(self, q: float = 0.6):
self.q = q
# 6 lỗ đúng bố cục bàn thật (pocket_name suy tên từ toạ độ)
self._pockets = [
np.array([0.0, 0.0]), # 0 góc dưới-trái
np.array([0.0, self.l / 2]), # 1 giữa-trái
np.array([0.0, self.l]), # 2 góc trên-trái
np.array([self.w, 0.0]), # 3 góc dưới-phải
np.array([self.w, self.l / 2]), # 4 giữa-phải
np.array([self.w, self.l]), # 5 góc trên-phải
]
def _position_q(self, cue_xy, nxt_xy) -> float:
return self.q
@pytest.fixture
def env_stub() -> EnvStub:
return EnvStub()
|