From 59e2b418c7e355636d158d8cd9f1fa5ee3618c3b Mon Sep 17 00:00:00 2001 From: Akashdeep Dhar Date: Tue, 8 Oct 2024 14:10:04 +0530 Subject: [PATCH] Fix `pytest-xdist` entrypoint with custom caching Also, add `tesseract` in the testing environment Add coverage for `conf.py` and make necessary changes as per suggestion Co-authored-by: Akashdeep Dhar Co-authored-by: Shounak Dey --- .github/workflows/test.yml | 1 + gi_loadouts/conf.py | 15 +++-- gi_loadouts/face/rsrc/__init__.py | 6 +- gi_loadouts/face/scan/work.py | 2 +- gi_loadouts/face/wind/calc.py | 2 +- test/conftest.py | 5 +- test/face/scan/test_scan.py | 107 ++++++++++++++++++------------ 7 files changed, 82 insertions(+), 56 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 47fff9b5..7a8c1a28 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,6 +26,7 @@ jobs: xcb-util-keysyms \ xcb-util-wm \ xorg-x11-server-Xvfb \ + tesseract \ --assumeyes - name: Install the base dependencies diff --git a/gi_loadouts/conf.py b/gi_loadouts/conf.py index 0411a968..60a9f3ef 100644 --- a/gi_loadouts/conf.py +++ b/gi_loadouts/conf.py @@ -1,10 +1,17 @@ from platform import system -if system() == "Windows": - tessexec = "C:\\Program Files\\Tesseract-OCR\\tesseract.exe" -else: - tessexec = "/usr/bin/tesseract" + +def get_tessexec_path(): + if system() == "Windows": + return "C:\\Program Files\\Tesseract-OCR\\tesseract.exe" + else: + return "/usr/bin/tesseract" + +tessexec = get_tessexec_path() tempname = "" temppath = "" stattime = 5000 + +data_prefix = "gi-loadouts-" +data_suffix = ".traineddata" diff --git a/gi_loadouts/face/rsrc/__init__.py b/gi_loadouts/face/rsrc/__init__.py index 0679ec95..fc8cb0da 100644 --- a/gi_loadouts/face/rsrc/__init__.py +++ b/gi_loadouts/face/rsrc/__init__.py @@ -22,7 +22,7 @@ def make_temp_file() -> None: Remove the residual cache data from the temporary directory left over during previous sessions due to unsuccessful termination before instantiating the same for this session. """ - ptrn = r"gi-loadouts-[a-z0-9_]+\.traineddata" + ptrn = fr"{conf.data_prefix}[a-z0-9_]+\{conf.data_suffix}" temp = Path(gettempdir()) resi = [temp / file.name for file in temp.iterdir() if file.is_file() if match(ptrn, file.name)] @@ -45,10 +45,10 @@ def make_temp_file() -> None: have to be created and deleted manually. On UNIX based operating systems like GNU/Linux or MacOS, files can be reliably opened even when they have been marked for deletion. """ - temp = NamedTemporaryFile(prefix="gi-loadouts-", suffix=".traineddata", delete=False, mode="w+b") + temp = NamedTemporaryFile(prefix=conf.data_prefix, suffix=conf.data_suffix, delete=False, mode="w+b") temp.write(cont) temp.close() - conf.tempname = Path(temp.name).name.replace(".traineddata", "") + conf.tempname = Path(temp.name).name.replace(conf.data_suffix, "") conf.temppath = temp.name diff --git a/gi_loadouts/face/scan/work.py b/gi_loadouts/face/scan/work.py index ffc0a487..0ae4285b 100644 --- a/gi_loadouts/face/scan/work.py +++ b/gi_loadouts/face/scan/work.py @@ -23,7 +23,7 @@ def __init__(self, snap: ImageFile): super().__init__() self.snap = snap - def scan_artifact(self) -> None: # pragma: no cover + def scan_artifact(self) -> None: # pragma: no cover """ Scan the screenshot for computing artifact information using Tesseract OCR diff --git a/gi_loadouts/face/wind/calc.py b/gi_loadouts/face/wind/calc.py index 60b8e74a..a382764b 100644 --- a/gi_loadouts/face/wind/calc.py +++ b/gi_loadouts/face/wind/calc.py @@ -9,7 +9,7 @@ class Assess: - def __init__(self) -> None: # pragma: no cover + def __init__(self) -> None: # pragma: no cover self.collection = Collection() self.c_team = None self.c_weap = None diff --git a/test/conftest.py b/test/conftest.py index e164e028..33ba8069 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -14,9 +14,8 @@ def runner(qtbot): @pytest.fixture def scantest(qtbot): """ - The codebase will automatically detect the part - once the artifact is changed manually in the window - or changed by the after the scan + The codebase will automatically detect the part once the artifact is changed manually in the + window or changed by the after the scan """ testscan = ScanDialog("fwol") qtbot.addWidget(testscan) diff --git a/test/face/scan/test_scan.py b/test/face/scan/test_scan.py index 6c014e05..d8ce306b 100644 --- a/test/face/scan/test_scan.py +++ b/test/face/scan/test_scan.py @@ -1,7 +1,7 @@ -from os import path, remove -from pathlib import Path +from os import remove from random import choice, randbytes -from tempfile import NamedTemporaryFile, gettempdir +from tempfile import NamedTemporaryFile +from uuid import uuid4 import pytest from PySide6.QtCore import Qt @@ -10,7 +10,7 @@ from gi_loadouts import __versdata__, conf from gi_loadouts.data.arti import __artilist__ -from gi_loadouts.face.rsrc import make_temp_file +from gi_loadouts.face.rsrc import kill_temp_file, make_temp_file from gi_loadouts.face.scan import file from gi_loadouts.face.util import truncate_text from gi_loadouts.type.arti import ArtiLevl, base @@ -42,27 +42,27 @@ def test_scan_window(scantest, _) -> None: "name, rare, part, part_type", [ pytest.param( - name, team.rare, team.fwol, "Flower of Life", id=f"face.scan.rule: Configuration Flower of Life artifact - {name}" + name, team.rare, team.fwol, "Flower of Life", id=f"face.scan.rule: Configuring Flower of Life artifact - {name}" ) for name, team in __artilist__.items() ] + [ pytest.param( - name, team.rare, team.pmod, "Plume of Death", id=f"face.scan.rule: Configuration Plume of Death artifact - {name}" + name, team.rare, team.pmod, "Plume of Death", id=f"face.scan.rule: Configuring Plume of Death artifact - {name}" ) for name, team in __artilist__.items() ] + [ pytest.param( - name, team.rare, team.sdoe, "Sands of Eon", id=f"face.scan.rule: Configuration Sands of Eon artifact - {name}" + name, team.rare, team.sdoe, "Sands of Eon", id=f"face.scan.rule: Configuring Sands of Eon artifact - {name}" ) for name, team in __artilist__.items() ] + [ pytest.param( - name, team.rare, team.gboe, "Goblet of Eonothem", id=f"face.scan.rule: Configuration Goblet of Eonothem artifact - {name}" + name, team.rare, team.gboe, "Goblet of Eonothem", id=f"face.scan.rule: Configuring Goblet of Eonothem artifact - {name}" ) for name, team in __artilist__.items() ] + [ pytest.param( - name, team.rare, team.ccol, "Circlet of Logos", id=f"face.scan.rule: Configuration Circlet of Logos artifact - {name}" + name, team.rare, team.ccol, "Circlet of Logos", id=f"face.scan.rule: Configuring Circlet of Logos artifact - {name}" ) for name, team in __artilist__.items() ] ) @@ -88,6 +88,7 @@ def test_scan_arti_drop(scantest, name, rare, part, part_type) -> None: scantest.arti_levl.setCurrentText(conf["levl"].value.name) conf["stat"] = choice([item for item in getattr(base, __dist__[conf["dist"]]["list"].__name__) if item.value != STAT.none]) scantest.arti_name_main.setCurrentText(conf["stat"].value.value) + """ Confirm if the user interface elements change accordingly """ @@ -142,20 +143,20 @@ def test_scan_arti_load(scantest, qtbot, mocker, _) -> None: """ """ - Create the tesseract training data + Create the Tesseract training data """ + mocker.patch("gi_loadouts.conf.data_prefix", f"{uuid4().hex.upper()[0:8]}-") make_temp_file() """ Perform the action of loading the artifact information """ - temp_prm = "" tempfile = "test/static/img/gi-loadouts-ocr-test.webp" - savefile = str(Path(gettempdir()) / "gi-loadouts-ocr-test.webp") + savefile = NamedTemporaryFile(prefix="gi-loadouts-ocr-test", suffix=".webp", delete=False, mode="wb") with open(tempfile, "rb") as src_file: - with open(savefile, "wb") as dest_file: - dest_file.write(src_file.read()) - mocker.patch.object(QFileDialog, "getOpenFileName", return_value=(savefile, temp_prm)) + savefile.write(src_file.read()) + savefile.close() + mocker.patch.object(QFileDialog, "getOpenFileName", return_value=(savefile.name, "")) qtbot.mouseClick(scantest.arti_cnvs_load, Qt.LeftButton) """ @@ -183,10 +184,9 @@ def check_label(): qtbot.waitUntil(check_label) """ - Clear the copied file from temp directory + Cleanup the temporary files from temp directory """ - if path.exists(savefile): - remove(savefile) + remove(savefile.name) @pytest.mark.parametrize( @@ -259,13 +259,17 @@ def test_scan_arti_load_fail(scantest, qtbot, mocker, _) -> None: """ """ - Perform the action of loading the artifact snapshot + Create a temporary file filled with random data to simulate a failure when trying to open an + image using PIL.Image.open(). """ - temp_prm = "" savefile = NamedTemporaryFile(prefix="gi-loadouts-", suffix=".webp", delete=False, mode="wb") savefile.write(randbytes(512*1024)) savefile.close() - mocker.patch.object(QFileDialog, "getOpenFileName", return_value=(savefile.name, temp_prm)) + + """ + Perform the action of loading the artifact snapshot + """ + mocker.patch.object(QFileDialog, "getOpenFileName", return_value=(savefile.name, "")) qtbot.mouseClick(scantest.arti_cnvs_load, Qt.LeftButton) """ @@ -278,30 +282,40 @@ def test_scan_arti_load_fail(scantest, qtbot, mocker, _) -> None: assert scantest.dialog.isVisible() """ - Cleanup the temporary files + Cleanup the temporary files from temp directory """ remove(savefile.name) @pytest.mark.parametrize( - "_", + "platform, tempexec", [ - pytest.param(None, id="face.scan.rule: Actual loading of Tesseract OCR executable") + pytest.param("Linux", "/usr/bin/tesseract", id="face.scan.rule: Actual loading of Tesseract OCR executable in Linux"), + pytest.param("Windows", "C:\\Program Files\\Tesseract-OCR\\tesseract.exe", id="face.scan.rule: Actual loading of Tesseract OCR executable in Windows") ] ) -def test_scan_tessexec_load(scantest, qtbot, mocker, _) -> None: +def test_scan_tessexec_load(scantest, qtbot, mocker, platform, tempexec) -> None: """ Attempt actual loading of Tesseract OCR executable :return: """ + """ + Store the initial conf.tessexec so that after this test it can be restored to the original value + """ + temp = conf.tessexec + + """ + Mock the system environment and reload the tessexec variable to apply the mocked path + """ + mocker.patch("gi_loadouts.conf.system", return_value=platform) + conf.tessexec = conf.get_tessexec_path() + """ Perform the action of loading the actual Tesseract OCR executable """ - tempexec = "/usr/bin/tesseract" - temp_prm = "" - mocker.patch.object(QFileDialog, "getOpenFileName", return_value=(tempexec, temp_prm)) + mocker.patch.object(QFileDialog, "getOpenFileName", return_value=(tempexec, "")) qtbot.mouseClick(scantest.arti_cnvs_conf, Qt.LeftButton) """ @@ -309,6 +323,11 @@ def test_scan_tessexec_load(scantest, qtbot, mocker, _) -> None: """ assert conf.tessexec == tempexec + """ + Reinstate the path for Tesseract OCR executable + """ + conf.tessexec = temp + @pytest.mark.parametrize( "_", @@ -356,18 +375,18 @@ def test_scan_register_fail(scantest, qtbot, mocker, expt) -> None: """ Create the tesseract training data """ + mocker.patch("gi_loadouts.conf.data_prefix", f"{uuid4().hex.upper()[0:8]}-") make_temp_file() """ Perform the action of loading the artifact information """ - temp_prm = "" tempfile = "test/static/img/gi-loadouts-ocr-test.webp" - savefile = str(Path(gettempdir()) / "gi-loadouts-ocr-test.webp") + savefile = NamedTemporaryFile(prefix="gi-loadouts-ocr-test", suffix=".webp", delete=False, mode="wb") with open(tempfile, "rb") as src_file: - with open(savefile, "wb") as dest_file: - dest_file.write(src_file.read()) - mocker.patch.object(QFileDialog, "getOpenFileName", return_value=(savefile, temp_prm)) + savefile.write(src_file.read()) + savefile.close() + mocker.patch.object(QFileDialog, "getOpenFileName", return_value=(savefile.name, "")) mocker.patch("gi_loadouts.face.scan.work.image_to_string", side_effect=expt) qtbot.mouseClick(scantest.arti_cnvs_load, Qt.LeftButton) @@ -389,10 +408,10 @@ def check_label(): qtbot.waitUntil(check_label) """ - Clear the copied file from temp directory + Cleanup the temporary files from temp directory """ - if path.exists(savefile): - remove(savefile) + kill_temp_file() + remove(savefile.name) @pytest.mark.parametrize( @@ -435,18 +454,18 @@ def test_scan_import_arti(scantest, qtbot, mocker, _) -> None: """ Create the tesseract training data """ + mocker.patch("gi_loadouts.conf.data_prefix", f"{uuid4().hex.upper()[0:8]}-") make_temp_file() """ Perform the action of loading the artifact information """ - temp_prm = "" tempfile = "test/static/img/gi-loadouts-ocr-test.webp" - savefile = str(Path(gettempdir()) / "gi-loadouts-ocr-test.webp") + savefile = NamedTemporaryFile(prefix="gi-loadouts-ocr-test", suffix=".webp", delete=False, mode="wb") with open(tempfile, "rb") as src_file: - with open(savefile, "wb") as dest_file: - dest_file.write(src_file.read()) - mocker.patch.object(QFileDialog, "getOpenFileName", return_value=(savefile, temp_prm)) + savefile.write(src_file.read()) + savefile.close() + mocker.patch.object(QFileDialog, "getOpenFileName", return_value=(savefile.name, "")) qtbot.mouseClick(scantest.arti_cnvs_load, Qt.LeftButton) init = scantest.arti_type.currentText() @@ -463,7 +482,7 @@ def test_scan_import_arti(scantest, qtbot, mocker, _) -> None: assert scantest.keep_info() == __rtrn__ """ - Clear the copied file from temp directory + Cleanup the temporary files from temp directory """ - if path.exists(savefile): - remove(savefile) + kill_temp_file() + remove(savefile.name)