import shutil from pathlib import Path import pytest import analyze MFG_IMAGES = sorted(Path("tests/images/manufacturing").glob("*/*.png")) INV_IMAGES = sorted(Path("tests/images/inventory").glob("*.png")) assert len(MFG_IMAGES) > 0, "no images found under tests/images/manufacturing" assert len(INV_IMAGES) > 0, "no images found under tests/images/inventory" OCR_AVAILABLE = analyze.pytesseract is not None and shutil.which("tesseract") is not None pytestmark = pytest.mark.skipif( not OCR_AVAILABLE, reason="pytesseract or tesseract-ocr not installed", ) # Pinned Manufacturing title-bar positions per fixture frame, measured from the # real captures (the window was dragged to 5 different places across sessions). MFG_POSITION = { "snapshots_f00000.png": (488, 480), "snapshots_f00002.png": (488, 480), "snapshots_f00003.png": (488, 480), "snapshots_f00006.png": (376, 262), "snapshots_f00007.png": (1330, 194), "snapshots_f00010.png": (1005, 565), "snapshots_f00013.png": (281, 676), "snapshots_f00015.png": (281, 676), "snapshots2_f00000.png": (1104, 327), "snapshots2_f00001.png": (1104, 327), "snapshots2_f00002.png": (1104, 327), "snapshots2_f00003.png": (1104, 327), "snapshots2_f00007.png": (1104, 327), "snapshots2_f00013.png": (1104, 327), "snapshots2_f00033.png": (1104, 337), "harvest_frame_00000.png": (173, 771), } # The Inventory window never moved across the capture sessions; every fixture # frame pins the same title-bar position. INV_POSITION = (1629, 686) # find_windows recovery is a full-frame OCR scan (~7s/frame) so it is only # exercised on a representative subset. "inventory": None for the harvesting # frame is a known miss: the full-frame scan does not read its Inventory title. RECOVERY_CASES = { "snapshots_f00000.png": ((488, 480), (1629, 686)), "snapshots_f00013.png": ((281, 676), (1629, 686)), "snapshots2_f00003.png": ((1104, 327), (1629, 686)), "snapshots2_f00013.png": ((1104, 327), (1629, 686)), "snapshots2_f00033.png": ((1104, 337), (1629, 686)), "harvest_frame_00000.png": ((173, 771), None), } _TOLERANCE = 15 def _load(path): image = analyze.load_image(path) assert image is not None, f"cannot load {path}" return image def _find_fixture(name): for root in list(Path("tests/images/manufacturing").glob("*")) + [Path("tests/images/inventory")]: candidate = root / name if candidate.exists(): return candidate raise AssertionError(f"no fixture file named {name}") def _mfg_positions(): assert set(MFG_POSITION) == {p.name for p in MFG_IMAGES}, "MFG_POSITION does not match fixture files" for path in MFG_IMAGES: yield path, MFG_POSITION[path.name] def test_manufacturing_title_is_verified_at_pinned_position(): for path, pos in _mfg_positions(): image = _load(path) assert analyze.verify_window(image, pos[0], pos[1], "manufacturing") is True, path def test_inventory_title_is_verified_at_pinned_position(): for path in INV_IMAGES: image = _load(path) assert analyze.verify_window(image, INV_POSITION[0], INV_POSITION[1], "inventory") is True, path def test_manufacturing_message_category_matches_folder(): for path in MFG_IMAGES: image = _load(path) x, y = MFG_POSITION[path.name] message = analyze.read_manufacturing_message(image, x, y) assert message is not None, path assert message["category"] == path.parent.name, path def test_find_windows_recovers_pinned_positions(): for name, (exp_mfg, exp_inv) in RECOVERY_CASES.items(): image = _load(_find_fixture(name)) found = analyze.find_windows(image) if exp_mfg is None: assert found["manufacturing"] is None, name else: pos = found["manufacturing"] assert pos is not None, f"manufacturing not recovered: {name}" assert abs(pos[0] - exp_mfg[0]) <= _TOLERANCE, name assert abs(pos[1] - exp_mfg[1]) <= _TOLERANCE, name if exp_inv is None: assert found["inventory"] is None, name else: pos = found["inventory"] assert pos is not None, f"inventory not recovered: {name}" assert abs(pos[0] - exp_inv[0]) <= _TOLERANCE, name assert abs(pos[1] - exp_inv[1]) <= _TOLERANCE, name def test_classify_message_maps_real_strings(): # Ground-truth texts from the captured frames, one per category. assert analyze.classify_message("you stopped working.", "red") == "stopped" assert analyze.classify_message("you started working...", "green") == "working" assert analyze.classify_message("you successfully created 1 thread", "green") == "success" assert analyze.classify_message("you failed to create a[n] thread", "red") == "failed" assert ( analyze.classify_message( "you failed to create a[n] potion of minor healing, and lost the ingredients", "red" ) == "failed_lost" ) assert analyze.classify_message( "you are hungry, you can't manufacture anything. eat until your food level is over 0", "red" ) == "hungry" assert analyze.classify_message("you are overloaded, you can't carry 1 thread!", "red") == "overloaded" assert analyze.classify_message("", "green") == "none" assert analyze.classify_message("garbled text", "red") == "unknown"