name: CI on: push: branches: [main, stable] pull_request: branches: [main, stable] # Cancel in-progress runs on new push concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true jobs: install-and-test: name: Install & Test (Windows) runs-on: windows-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.10" cache: "pip" - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements-win11.txt shell: bash - name: Install Tesseract OCR run: choco install tesseract --no-progress -y - name: Python version run: python -c "import sys; print(sys.version)" - name: Syntax check env: PYTHONPATH: ./src run: python -m compileall -q src tools test scripts - name: Verify core imports env: PYTHONPATH: ./src run: | python -c " import sys sys.path.insert(0, 'src') modules = [ 'config', 'logger', 'screen', 'pather', 'template_finder', 'game_stats', 'health_manager', 'death_manager', 'd2r_image', 'item', 'item.pickit', 'transmute', 'shop', 'char', 'utils', 'utils.os_detect', 'messages', ] for mod in modules: try: __import__(mod) print(f' {mod}: OK') except ImportError as e: print(f' {mod}: FAILED - {e}') sys.exit(1) print('All core imports successful.') " - name: Verify botty runs (import main modules) env: PYTHONPATH: ./src run: | python -c " import sys, os sys.path.insert(0, 'src') import ssl ssl.SSLContext.load_default_certs = lambda *a, **k: None from version import __version__ print(f' Version: {__version__}') from config import Config print(' Config: OK') from game_controller import GameController print(' GameController: OK') from bot import Bot print(' Bot: OK') from run.diablo import Diablo print(' Diablo run: OK') from run.pindle import Pindle print(' Pindle run: OK') from run.arcane import Arcane print(' Arcane run: OK') from run.vizier import Vizier print(' Vizier run: OK') print('All botty entry modules import successfully.') " - name: Verify OCR (pytesseract) env: PYTHONPATH: ./src run: | python -c " import sys, os, tempfile sys.path.insert(0, 'src') import pytesseract pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe' import cv2 import numpy as np version = pytesseract.get_tesseract_version() print(f' Tesseract version: {version}') # Create a simple test image with text img = np.full((50, 200), 255, dtype=np.uint8) cv2.putText(img, 'Hello Botty', (20, 35), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 0), 1) with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as f: cv2.imwrite(f.name, img) text = pytesseract.image_to_string(f.name, lang='eng').strip() print(f' OCR result: {text}') print('OCR pytesseract: OK') " - name: Verify OCR (botty ocr module) env: PYTHONPATH: ./src PYTESSERACT_TESSERACT_CMD: C:\Program Files\Tesseract-OCR\tesseract.exe run: | python -c " import sys, os sys.path.insert(0, 'src') # Import the botty OCR module - it reads PYTESSERACT_TESSERACT_CMD from env from d2r_image.ocr import image_to_text, pytesseract import cv2 import numpy as np import tempfile if pytesseract is None: print(' pytesseract not available — skipping') sys.exit(0) # Create test image (3-channel for invert) img = np.full((50, 200, 3), 255, dtype=np.uint8) cv2.putText(img, '123', (80, 35), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 0), 2) # Run through botty's image_to_text results = image_to_text( [img], model='hover-eng_inconsolata_inv_th_fast', psm=7, crop_pad=False, invert=True, threshold=25, ) result_text = results[0].text if results else 'empty' print(f' OCR result: {result_text}') print('OCR botty module: OK') " - name: Log analyzer tests (self-healing framework) env: PYTHONPATH: ./src:. RUN_ENV: test run: | python -m pytest test/auto/test_log_analyzer.py -v --tb=short - name: Tests with coverage env: PYTHONPATH: ./src:. RUN_ENV: test run: | python -m coverage run -m pytest -v --tb=short --ignore=test/auto/test_self_healing.py --ignore=test/smoke_test.py --ignore=test/test_version_consistency.py python -m coverage xml --ignore-errors || true - name: Upload coverage uses: actions/upload-artifact@v4 if: always() # Non-blocking: this is a convenience artifact, not a gate. When the account's # artifact storage quota is full the upload fails with # "Failed to CreateArtifact: Artifact storage quota has been hit" and marked the # whole run red even though every test step passed. A quota problem is not a # broken build. continue-on-error: true with: name: coverage-report path: coverage.xml retention-days: 7