feat: harden install.bat + add CI checks for launcher scripts
install.bat: - Add conda self-test (conda --version) before env create - Verify botty python.exe exists after env creation - Smoke-test key imports (cv2, tesserocr, discord, etc.) CI (.github/workflows/ci.yml): - Add test_setup_bat_files.py to test matrix test/test_setup_bat_files.py (7 tests): - All expected .bat files exist - No hardcoded usernames (alex, alexpolo, ultimate) in run scripts - No absolute home paths in run scripts (must use %~dp0 / %USERNAME%) - All run_*.bat source find_python.bat (no duplicated conda detection) - find_python.bat checks >= 6 conda locations - install.bat has conda self-test and python verification
This commit is contained in:
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
run: |
|
||||
C:\Miniconda\condabin\conda.bat activate botty
|
||||
python -c "import sys; print(sys.version)"
|
||||
coverage run -m pytest test/smoke_test.py test/nip/ test/game_stats_test.py test/closest_non_hud_test.py test/transmute/ -v -s
|
||||
coverage run -m pytest test/smoke_test.py test/nip/ test/game_stats_test.py test/closest_non_hud_test.py test/transmute/ test/test_setup_bat_files.py -v -s
|
||||
|
||||
- name: Coverage
|
||||
shell: powershell
|
||||
|
||||
51
install.bat
51
install.bat
@@ -35,6 +35,20 @@ exit /b 1
|
||||
echo Found conda: %CONDA_EXE%
|
||||
echo.
|
||||
|
||||
:: --- Test conda works (catches broken base env, missing pywin32, etc.) ---
|
||||
echo Testing conda...
|
||||
"%CONDA_EXE%" --version >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
echo.
|
||||
echo ERROR: conda found but failed to run. Your conda installation may be broken.
|
||||
echo Try re-installing Miniforge: https://github.com/conda-forge/miniforge
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
echo conda is working.
|
||||
echo.
|
||||
|
||||
:: --- Create or update the botty env ---
|
||||
"%CONDA_EXE%" env list | findstr /C:"botty" >nul 2>&1
|
||||
if %errorlevel% equ 0 (
|
||||
@@ -52,6 +66,43 @@ if %errorlevel% neq 0 (
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:: --- Verify botty env has Python ---
|
||||
set "PYTHON="
|
||||
for %%C in (
|
||||
"%USERPROFILE%\miniforge3\envs\botty\python.exe"
|
||||
"%USERPROFILE%\miniconda3\envs\botty\python.exe"
|
||||
"%USERPROFILE%\anaconda3\envs\botty\python.exe"
|
||||
"%ProgramData%\miniforge3\envs\botty\python.exe"
|
||||
"%ProgramData%\miniconda3\envs\botty\python.exe"
|
||||
"%ProgramData%\anaconda3\envs\botty\python.exe"
|
||||
) do (
|
||||
if exist %%C (
|
||||
set "PYTHON=%%~C"
|
||||
goto :env_created
|
||||
)
|
||||
)
|
||||
|
||||
echo.
|
||||
echo ERROR: botty env was created but python.exe was not found.
|
||||
echo This usually means the env build failed. Check output above.
|
||||
pause
|
||||
exit /b 1
|
||||
|
||||
:env_created
|
||||
echo Botty Python: %PYTHON%
|
||||
|
||||
:: --- Smoke test: import key dependencies ---
|
||||
echo.
|
||||
echo Verifying dependencies...
|
||||
"%PYTHON%" -c "import cv2, mss, numpy, tesserocr, discord, transitions, rapidfuzz" >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
echo.
|
||||
echo WARNING: Some imports failed. Try running install.bat again, or check:
|
||||
echo development.md for manual troubleshooting.
|
||||
) else (
|
||||
echo All key dependencies verified.
|
||||
)
|
||||
|
||||
echo.
|
||||
echo ============================================
|
||||
echo Installation complete!
|
||||
|
||||
115
test/test_setup_bat_files.py
Normal file
115
test/test_setup_bat_files.py
Normal file
@@ -0,0 +1,115 @@
|
||||
"""Test that launcher bat files are well-formed and portable.
|
||||
|
||||
Checks:
|
||||
1. All expected .bat files exist
|
||||
2. No hardcoded usernames or absolute paths (except conda paths)
|
||||
3. find_python.bat is referenced by run_*.bat
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
EXPECTED_BATS = [
|
||||
"install.bat",
|
||||
"find_python.bat",
|
||||
"run_botty.bat",
|
||||
"run_asset_extractor.bat",
|
||||
"run_quest_debug.bat",
|
||||
]
|
||||
|
||||
USERNAMES_TO_BLOCK = ["alex", "alexpolo", "ultimate"]
|
||||
|
||||
|
||||
def _read(name):
|
||||
return open(os.path.join(ROOT, name)).read()
|
||||
|
||||
|
||||
def _bat_exists(name):
|
||||
assert os.path.isfile(os.path.join(ROOT, name)), f"{name} is missing from repo root"
|
||||
|
||||
|
||||
class TestBatFilesExist:
|
||||
def test_all_bats_present(self):
|
||||
for name in EXPECTED_BATS:
|
||||
_bat_exists(name)
|
||||
|
||||
|
||||
class TestNoHardcodedUsernames:
|
||||
"""run_*.bat and find_python.bat must never contain a real username."""
|
||||
|
||||
_CHECKED = [
|
||||
"find_python.bat",
|
||||
"run_botty.bat",
|
||||
"run_asset_extractor.bat",
|
||||
"run_quest_debug.bat",
|
||||
]
|
||||
|
||||
def test_no_hardcoded_usernames(self):
|
||||
for name in self._CHECKED:
|
||||
content = _read(name).lower()
|
||||
for uname in USERNAMES_TO_BLOCK:
|
||||
for line in content.split("\n"):
|
||||
stripped = line.strip().lstrip(":").lstrip("*")
|
||||
if uname in stripped and not stripped.startswith(":"):
|
||||
raise AssertionError(
|
||||
f"{name} contains hardcoded username '{uname}' on: "
|
||||
f"{line.strip()}"
|
||||
)
|
||||
|
||||
def test_no_absolute_home_paths(self):
|
||||
for name in self._CHECKED:
|
||||
content = _read(name)
|
||||
# Find C:\Users\ followed by a literal username (not %USERNAME%)
|
||||
bad = re.findall(r"C:\\Users\\([^%\s]+)", content)
|
||||
bad = [b for b in bad if b != "%USERNAME%"]
|
||||
assert not bad, f"{name} has hardcoded home path(s): {bad}"
|
||||
|
||||
|
||||
class TestFindPythonUsage:
|
||||
"""All run_*.bat should source find_python.bat."""
|
||||
|
||||
_RUN_BATS = [
|
||||
"run_botty.bat",
|
||||
"run_asset_extractor.bat",
|
||||
"run_quest_debug.bat",
|
||||
]
|
||||
|
||||
def test_run_bats_call_find_python(self):
|
||||
for name in self._RUN_BATS:
|
||||
content = _read(name)
|
||||
assert "find_python.bat" in content, (
|
||||
f"{name} does not call find_python.bat -- "
|
||||
f"source the shared helper instead of duplicating conda detection"
|
||||
)
|
||||
|
||||
def test_find_python_has_conda_locations(self):
|
||||
content = _read("find_python.bat")
|
||||
location_count = (
|
||||
content.count("miniforge3")
|
||||
+ content.count("miniconda3")
|
||||
+ content.count("anaconda3")
|
||||
)
|
||||
assert location_count >= 6, (
|
||||
f"find_python.bat only checks {location_count} conda locations, "
|
||||
f"expected >= 6"
|
||||
)
|
||||
|
||||
|
||||
class TestInstallBat:
|
||||
def test_conda_self_test(self):
|
||||
content = _read("install.bat")
|
||||
has_test = any(
|
||||
kw in content
|
||||
for kw in ["--version", "conda test", "testing conda", "test conda"]
|
||||
)
|
||||
assert has_test, (
|
||||
"install.bat should verify conda works before creating the env"
|
||||
)
|
||||
|
||||
def test_verifies_python_after_env_create(self):
|
||||
content = _read("install.bat")
|
||||
assert "python.exe" in content, (
|
||||
"install.bat should verify botty env python.exe exists after env create"
|
||||
)
|
||||
Reference in New Issue
Block a user