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/assets/wind.ui b/assets/wind.ui index b2a66ef0..6116cbed 100644 --- a/assets/wind.ui +++ b/assets/wind.ui @@ -3004,7 +3004,7 @@ - Load + Scan #arti_ccol_scan {font: 87 10pt "Font Awesome 6 Free Solid";} @@ -4055,7 +4055,7 @@ - Load + Scan #arti_gboe_scan {font: 87 10pt "Font Awesome 6 Free Solid";} @@ -4323,7 +4323,7 @@ - Load + Scan #arti_sdoe_scan {font: 87 10pt "Font Awesome 6 Free Solid";} @@ -4591,7 +4591,7 @@ - Load + Scan #arti_pmod_scan {font: 87 10pt "Font Awesome 6 Free Solid";} @@ -4859,7 +4859,7 @@ - Load + Scan #arti_fwol_scan {font: 87 10pt "Font Awesome 6 Free Solid";} 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/rule.py b/gi_loadouts/face/scan/rule.py index 60bfef2c..d77f9f83 100644 --- a/gi_loadouts/face/scan/rule.py +++ b/gi_loadouts/face/scan/rule.py @@ -237,8 +237,18 @@ def register_return_from_scanning(self, rslt: tuple) -> None: :return: """ + area, main, seco, team, levl, rare, duration, expt = rslt + + if expt: + self.show_dialog( + QMessageBox.Information, + "Faulty scanning", + f"Please consider checking your input after ensuring that the proper Tesseract OCR executable has been selected.\n\n{expt}" + ) + return + self.arti_shot.setPixmap(self.shot) - area, main, seco, team, levl, rare, duration = rslt + if area in [self.arti_dist.itemText(indx) for indx in range(self.arti_dist.count())]: self.arti_dist.setCurrentText(area) if team in [self.arti_type.itemText(indx) for indx in range(self.arti_type.count())]: diff --git a/gi_loadouts/face/scan/work.py b/gi_loadouts/face/scan/work.py index cd9f3d86..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: + def scan_artifact(self) -> None: # pragma: no cover """ Scan the screenshot for computing artifact information using Tesseract OCR @@ -31,6 +31,11 @@ def scan_artifact(self) -> None: :return: Collection of artifact information and duration of computation """ + """ + coverage.py does not seem to correctly work with the QThread. + Even though this part is tested but the coverage remains unchanged. https://github.com/nedbat/coveragepy/issues/686 + """ + strttime = time() area, main, seco, team, levl, rare = "", ATTR(), {"a": ATTR(), "b": ATTR(), "c": ATTR(), "d": ATTR()}, "", "Level 00", "Star 0" @@ -45,9 +50,13 @@ def scan_artifact(self) -> None: text = image_to_string(self.snap, lang=conf.tempname, config=f"--tessdata-dir {location}") except (OSError, TesseractError) as expt: if isinstance(expt, OSError): - raise OSError("Selected executable of Tesseract OCR is unfunctional.") from expt - elif isinstance(expt, TesseractError): - raise ValueError("Processing failed as either Tesseract OCR executable ceased to function or training data was tampered with.") from expt + expt = "Selected executable of Tesseract OCR is unfunctional." + else: + expt = "Processing failed as either Tesseract OCR executable ceased to function or training data was tampered with." + stoptime = time() + rslt = area, main, seco, team, levl, rare, (stoptime - strttime), expt + self.result.emit(rslt) + return # DISTRIBUTION AREA areadict = {} @@ -148,6 +157,6 @@ def scan_artifact(self) -> None: stoptime = time() - rslt = area, main, seco, team, levl, rare, (stoptime - strttime) + rslt = area, main, seco, team, levl, rare, (stoptime - strttime), None self.result.emit(rslt) diff --git a/gi_loadouts/face/wind/calc.py b/gi_loadouts/face/wind/calc.py index a4f864bc..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: + def __init__(self) -> None: # pragma: no cover self.collection = Collection() self.c_team = None self.c_weap = None diff --git a/gi_loadouts/face/wind/main.py b/gi_loadouts/face/wind/main.py index bfe449bf..2fa16636 100644 --- a/gi_loadouts/face/wind/main.py +++ b/gi_loadouts/face/wind/main.py @@ -82,7 +82,6 @@ def initialize_events(self) -> None: drop, text = getattr(self, f"arti_{part}_name_{alfa}"), getattr(self, f"arti_{part}_data_{alfa}") drop.currentTextChanged.connect(lambda _, a_drop=drop, a_text=text: self.render_lineedit_readonly_when_none(a_drop, a_text)) text.textChanged.connect(self.validate_lineedit_userdata) - for part in ["fwol", "pmod", "sdoe", "gboe", "ccol"]: getattr(self, f"arti_{part}_scan").clicked.connect(lambda _, a_part=part: self.show_scan_dialog(a_part)) getattr(self, f"arti_{part}_load").clicked.connect(lambda _, a_part=part: self.arti_load(a_part)) getattr(self, f"arti_{part}_save").clicked.connect(lambda _, a_part=part: self.arti_save(a_part)) diff --git a/gi_loadouts/face/wind/wind.py b/gi_loadouts/face/wind/wind.py index 8071dae8..491cf03d 100644 --- a/gi_loadouts/face/wind/wind.py +++ b/gi_loadouts/face/wind/wind.py @@ -2025,7 +2025,7 @@ def retranslateUi(self, mainwind): #endif // QT_CONFIG(tooltip) self.arti_ccol_wipe.setText(QCoreApplication.translate("mainwind", "trash", None)) #if QT_CONFIG(tooltip) - self.arti_ccol_scan.setToolTip(QCoreApplication.translate("mainwind", "Load", None)) + self.arti_ccol_scan.setToolTip(QCoreApplication.translate("mainwind", "Scan", None)) #endif // QT_CONFIG(tooltip) self.arti_ccol_scan.setText(QCoreApplication.translate("mainwind", "eye", None)) self.arti_ccol_data_d.setPlaceholderText(QCoreApplication.translate("mainwind", "Data", None)) @@ -2057,7 +2057,7 @@ def retranslateUi(self, mainwind): #endif // QT_CONFIG(tooltip) self.arti_gboe_wipe.setText(QCoreApplication.translate("mainwind", "trash", None)) #if QT_CONFIG(tooltip) - self.arti_gboe_scan.setToolTip(QCoreApplication.translate("mainwind", "Load", None)) + self.arti_gboe_scan.setToolTip(QCoreApplication.translate("mainwind", "Scan", None)) #endif // QT_CONFIG(tooltip) self.arti_gboe_scan.setText(QCoreApplication.translate("mainwind", "eye", None)) #if QT_CONFIG(tooltip) @@ -2077,7 +2077,7 @@ def retranslateUi(self, mainwind): #endif // QT_CONFIG(tooltip) self.arti_sdoe_wipe.setText(QCoreApplication.translate("mainwind", "trash", None)) #if QT_CONFIG(tooltip) - self.arti_sdoe_scan.setToolTip(QCoreApplication.translate("mainwind", "Load", None)) + self.arti_sdoe_scan.setToolTip(QCoreApplication.translate("mainwind", "Scan", None)) #endif // QT_CONFIG(tooltip) self.arti_sdoe_scan.setText(QCoreApplication.translate("mainwind", "eye", None)) #if QT_CONFIG(tooltip) @@ -2097,7 +2097,7 @@ def retranslateUi(self, mainwind): #endif // QT_CONFIG(tooltip) self.arti_pmod_wipe.setText(QCoreApplication.translate("mainwind", "trash", None)) #if QT_CONFIG(tooltip) - self.arti_pmod_scan.setToolTip(QCoreApplication.translate("mainwind", "Load", None)) + self.arti_pmod_scan.setToolTip(QCoreApplication.translate("mainwind", "Scan", None)) #endif // QT_CONFIG(tooltip) self.arti_pmod_scan.setText(QCoreApplication.translate("mainwind", "eye", None)) #if QT_CONFIG(tooltip) @@ -2117,7 +2117,7 @@ def retranslateUi(self, mainwind): #endif // QT_CONFIG(tooltip) self.arti_fwol_wipe.setText(QCoreApplication.translate("mainwind", "trash", None)) #if QT_CONFIG(tooltip) - self.arti_fwol_scan.setToolTip(QCoreApplication.translate("mainwind", "Load", None)) + self.arti_fwol_scan.setToolTip(QCoreApplication.translate("mainwind", "Scan", None)) #endif // QT_CONFIG(tooltip) self.arti_fwol_scan.setText(QCoreApplication.translate("mainwind", "eye", None)) self.pair_area_desc.setDocumentTitle(QCoreApplication.translate("mainwind", "pair_area_desc", None)) diff --git a/test/conftest.py b/test/conftest.py index 4b0e4748..33ba8069 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,5 +1,6 @@ import pytest +from gi_loadouts.face.scan.main import ScanDialog from gi_loadouts.face.wind.main import MainWindow @@ -8,3 +9,14 @@ def runner(qtbot): testwind = MainWindow() qtbot.addWidget(testwind) return testwind + + +@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 + """ + testscan = ScanDialog("fwol") + qtbot.addWidget(testscan) + return testscan diff --git a/test/face/scan/__init__.py b/test/face/scan/__init__.py new file mode 100644 index 00000000..20e0b18f --- /dev/null +++ b/test/face/scan/__init__.py @@ -0,0 +1,32 @@ +from gi_loadouts.type.arti.base import ( + MainStatType_CCOL, + MainStatType_FWOL, + MainStatType_GBOE, + MainStatType_PMOD, + MainStatType_SDOE, +) +from gi_loadouts.type.stat import ATTR, STAT + +__dist__ = { + "Flower of Life": {"list": MainStatType_FWOL, "part": "fwol"}, + "Plume of Death": {"list": MainStatType_PMOD, "part": "pmod"}, + "Sands of Eon": {"list": MainStatType_SDOE, "part": "sdoe"}, + "Goblet of Eonothem": {"list": MainStatType_GBOE, "part": "gboe"}, + "Circlet of Logos": {"list": MainStatType_CCOL, "part": "ccol"}, +} + +__rtrn__ = { + "part": "sdoe", + "team": "Shimenawa's Reminiscence", + "rare": "Star 5", + "levl": "Level 20", + "stat": { + "main": ATTR(stat_name=STAT.energy_recharge_perc, stat_data=51.8), + "seco": { + "a": ATTR(stat_name=STAT.elemental_mastery, stat_data=23.0), + "b": ATTR(stat_name=STAT.critical_rate_perc, stat_data=6.6), + "c": ATTR(stat_name=STAT.critical_damage_perc, stat_data=21.0), + "d": ATTR(stat_name=STAT.health_points_perc, stat_data=13.4) + } + } +} diff --git a/test/face/scan/test_scan.py b/test/face/scan/test_scan.py new file mode 100644 index 00000000..ed9b9979 --- /dev/null +++ b/test/face/scan/test_scan.py @@ -0,0 +1,490 @@ +from os import remove +from random import choice, randbytes +from tempfile import NamedTemporaryFile +from uuid import uuid4 + +import pytest +from PySide6.QtCore import Qt +from PySide6.QtWidgets import QDialog, QFileDialog, QMessageBox +from pytesseract import TesseractError + +from gi_loadouts import __versdata__, conf +from gi_loadouts.data.arti import __artilist__ +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 +from gi_loadouts.type.stat import STAT +from test.face.scan import __dist__, __rtrn__ + + +@pytest.mark.parametrize( + "_", + [ + pytest.param(None, id="face.scan: Testing ScanWindow") + ] +) +def test_scan_window(scantest, _) -> None: + """ + Testing ScanWindow + + :return: + """ + + """ + Confirm if the user interface elements change accordingly + """ + assert isinstance(scantest, QDialog) + assert scantest.windowTitle() == f"Loadouts for Genshin Impact v{__versdata__}" + + +@pytest.mark.parametrize( + "name, rare, part, part_type", + [ + pytest.param( + 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: 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: 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: 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: Configuring Circlet of Logos artifact - {name}" + ) for name, team in __artilist__.items() + ] +) +def test_scan_arti_drop(scantest, name, rare, part, part_type) -> None: + """ + Test the configuration of artifact on the user interface + + :return: + """ + + """ + Set the user interface elements as intended + """ + conf = dict() + + conf["dist"] = part_type + scantest.arti_dist.setCurrentText(conf["dist"]) + conf["name"] = name + scantest.arti_type.setCurrentText(conf["name"]) + conf["rare"] = choice([item for item in rare]) + scantest.arti_rare.setCurrentText(conf["rare"].value.name) + conf["levl"] = choice([item for item in ArtiLevl if conf["rare"] in item.value.rare]) + 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 + """ + if name == "None": + return + + assert scantest.arti_type_name.text() == truncate_text(part.__name__, 34) + assert scantest.arti_data_main.text() == str(round(part.stat_data, 1)) + + +@pytest.mark.parametrize( + "_", + [ + pytest.param(None, id="face.scan.rule: Clearing artifact") + ] +) +def test_scan_arti_wipe(scantest, qtbot, _) -> None: + """ + Test the clearing of the artifact + + :return: + """ + + """ + Set the user interface elements as intended + """ + qtbot.mouseClick(scantest.arti_back_wipe, Qt.LeftButton) + + """ + Confirm if the user interface elements change accordingly + """ + assert scantest.arti_type.currentText() == "None" + assert scantest.arti_levl.currentText() == "None" + assert scantest.arti_rare.currentText() == "Star 0" + assert scantest.arti_type_name.text() == "None" + for item in ["main", "a", "b", "c", "d"]: + assert getattr(scantest, f"arti_name_{item}").currentText() == "None" + assert getattr(scantest, f"arti_data_{item}").text() == "" + + +@pytest.mark.parametrize( + "_", + [ + pytest.param(None, id="face.scan.rule: Test OCR functionality") + ] +) +def test_scan_arti_load(scantest, qtbot, mocker, _) -> None: + """ + Test OCR functionality + + :return: + """ + + """ + 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 + """ + tempfile = "test/static/img/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: + savefile.write(src_file.read()) + savefile.close() + mocker.patch.object(QFileDialog, "getOpenFileName", return_value=(savefile.name, "")) + qtbot.mouseClick(scantest.arti_cnvs_load, Qt.LeftButton) + + """ + Confirm if the user interface elements change accordingly + """ + def check_label(): + """ + Checking the interface elements change asynchronously + """ + assert scantest.arti_text.text() == "Browse your local storage to load a high quality screenshot of your artifact and the statistics will automatically be computed from there." + assert scantest.arti_type.currentText() == "Shimenawa's Reminiscence" + assert scantest.arti_levl.currentText() == "Level 20" + assert scantest.arti_rare.currentText() == "Star 5" + assert scantest.arti_type_name.text() == "Morning Dew's Moment" + assert scantest.arti_name_main.currentText() == "Energy Recharge" + assert scantest.arti_data_main.text() == "51.8" + assert scantest.arti_name_a.currentText() == "Elemental Mastery" + assert scantest.arti_data_a.text() == "23.0" + assert scantest.arti_name_b.currentText() == "Crit Rate" + assert scantest.arti_data_b.text() == "6.6" + assert scantest.arti_name_c.currentText() == "Crit DMG" + assert scantest.arti_data_c.text() == "21.0" + assert scantest.arti_name_d.currentText() == "HP %" + assert scantest.arti_data_d.text() == "13.4" + + qtbot.waitUntil(check_label) + + """ + Cleanup the temporary files from temp directory + """ + remove(savefile.name) + + +@pytest.mark.parametrize( + "_", + [ + pytest.param(None, id="face.scan.rule: Cancelling loading an artifact snapshot") + ] +) +def test_scan_arti_load_nope(scantest, qtbot, mocker, _) -> None: + """ + Attempt cancelling loading an artifact snapshot + + :return: + """ + + """ + Store the artifact information before trying to load + """ + init_arti_type = scantest.arti_type.currentText() + init_arti_levl = scantest.arti_levl.currentText() + init_arti_rare = scantest.arti_rare.currentText() + init_arti_type_name = scantest.arti_type_name.text() + init_arti_name_main = scantest.arti_name_main.currentText() + init_arti_data_main = scantest.arti_data_main.text() + init_arti_name_a = scantest.arti_name_a.currentText() + init_arti_data_a = scantest.arti_data_a.text() + init_arti_name_b = scantest.arti_name_b.currentText() + init_arti_data_b = scantest.arti_data_b.text() + init_arti_name_c = scantest.arti_name_c.currentText() + init_arti_data_c = scantest.arti_data_c.text() + init_arti_name_d = scantest.arti_name_d.currentText() + init_arti_data_d = scantest.arti_data_d.text() + + """ + Perform the action of loading the artifact information + """ + mocker.patch.object(file.FileHandling, "load_screenshot", return_value=(False, None, None)) + qtbot.mouseClick(scantest.arti_cnvs_load, Qt.LeftButton) + + """ + Confirm if the user interface elements stayed the same + """ + assert scantest.arti_type.currentText() == init_arti_type + assert scantest.arti_levl.currentText() == init_arti_levl + assert scantest.arti_rare.currentText() == init_arti_rare + assert scantest.arti_type_name.text() == init_arti_type_name + assert scantest.arti_name_main.currentText() == init_arti_name_main + assert scantest.arti_data_main.text() == init_arti_data_main + assert scantest.arti_name_a.currentText() == init_arti_name_a + assert scantest.arti_data_a.text() == init_arti_data_a + assert scantest.arti_name_b.currentText() == init_arti_name_b + assert scantest.arti_data_b.text() == init_arti_data_b + assert scantest.arti_name_c.currentText() == init_arti_name_c + assert scantest.arti_data_c.text() == init_arti_data_c + assert scantest.arti_name_d.currentText() == init_arti_name_d + assert scantest.arti_data_d.text() == init_arti_data_d + + +@pytest.mark.parametrize( + "_", + [ + pytest.param(None, id="face.scan.rule: Failing to load the artifact snapshot") + ] +) +def test_scan_arti_load_fail(scantest, qtbot, mocker, _) -> None: + """ + Attempt failing to loading the artifact snapshot + + :return: + """ + + """ + Create a temporary file filled with random data to simulate a failure when trying to open an + image using PIL.Image.open(). + """ + savefile = NamedTemporaryFile(prefix="gi-loadouts-", suffix=".webp", delete=False, mode="wb") + savefile.write(randbytes(512*1024)) + savefile.close() + + """ + 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) + + """ + Confirm if the user interface elements change accordingly + """ + assert isinstance(scantest.dialog, QMessageBox) + assert scantest.dialog.icon() == QMessageBox.Information + assert scantest.dialog.windowTitle() == "Faulty scanning" + assert "Please select an accurate screenshot." in scantest.dialog.text() + assert scantest.dialog.isVisible() + + """ + Cleanup the temporary files from temp directory + """ + remove(savefile.name) + + +@pytest.mark.parametrize( + "platform, tempexec", + [ + 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, 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 + """ + mocker.patch.object(QFileDialog, "getOpenFileName", return_value=(tempexec, "")) + qtbot.mouseClick(scantest.arti_cnvs_conf, Qt.LeftButton) + + """ + Confirm if the user interface elements change accordingly + """ + assert conf.tessexec == tempexec + + """ + Reinstate the path for Tesseract OCR executable + """ + conf.tessexec = temp + + +@pytest.mark.parametrize( + "_", + [ + pytest.param(None, id="face.scan.rule: Failing to load the Tesseract OCR executable") + ] +) +def test_scan_tessexec_load_fail(scantest, qtbot, mocker, _) -> None: + """ + Attempt failing to load the Tesseract OCR executable + + :return: + """ + + """ + Perform the action of loading the Tesseract OCR executable + """ + mocker.patch.object(file.FileHandling, "load_tessexec", side_effect=Exception) + qtbot.mouseClick(scantest.arti_cnvs_conf, Qt.LeftButton) + + """ + Confirm if the user interface elements change accordingly + """ + assert isinstance(scantest.dialog, QMessageBox) + assert scantest.dialog.icon() == QMessageBox.Information + assert scantest.dialog.windowTitle() == "Faulty scanning" + assert "Please consider checking your input after ensuring that the proper Tesseract OCR executable has been selected." in scantest.dialog.text() + assert scantest.dialog.isVisible() + + +@pytest.mark.parametrize( + "expt", + [ + pytest.param(OSError, id="face.scan.rule: Failing of register function with OSError"), + pytest.param(TesseractError("abc", "xyz"), id="face.scan.rule: Failing of register function with TesseractError") + ] +) +def test_scan_register_fail(scantest, qtbot, mocker, expt) -> None: + """ + Attempt failing the register function with OSError and TesseractError + + :return: + """ + + """ + 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 + """ + tempfile = "test/static/img/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: + 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) + + """ + Confirm if the user interface elements change accordingly + """ + def check_label(): + """ + Checking the interface elements change asynchronously + """ + assert isinstance(scantest.dialog, QMessageBox) + assert scantest.dialog.icon() == QMessageBox.Information + assert scantest.dialog.windowTitle() == "Faulty scanning" + if isinstance(expt, OSError): + assert "Selected executable of Tesseract OCR is unfunctional." in scantest.dialog.text() + elif isinstance(expt, TesseractError): + assert "Processing failed as either Tesseract OCR executable ceased to function or training data was tampered with." in scantest.dialog.text() + assert scantest.dialog.isVisible() + + qtbot.waitUntil(check_label) + + """ + Cleanup the temporary files from temp directory + """ + kill_temp_file() + remove(savefile.name) + + +@pytest.mark.parametrize( + "_", + [ + pytest.param(None, id="face.scan.rule: Clearing Snapshot") + ] +) +def test_scan_snapshot_wipe(scantest, qtbot, _) -> None: + """ + Attempt clearing of the snapshot + + :return: + """ + + """ + Perform the action to clear the snapshot + """ + qtbot.mouseClick(scantest.arti_cnvs_wipe, Qt.LeftButton) + + """ + Confirm if the user interface elements change accordingly + """ + assert scantest.arti_shot.text() == "YOUR ARTIFACT SCREENSHOT WILL SHOW UP HERE" + + +@pytest.mark.parametrize( + "_", + [ + pytest.param(None, id="face.scan.rule: Importing artifact information in MainWindow") + ] +) +def test_scan_import_arti(scantest, qtbot, mocker, _) -> None: + """ + Attempt importing artifact information in MainWindow + + :return: + """ + + """ + 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 + """ + tempfile = "test/static/img/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: + 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() + qtbot.waitUntil(lambda: scantest.arti_type.currentText() != init) + + """ + Perform the action of importing artifact info in MainWindow + """ + qtbot.mouseClick(scantest.arti_back_done, Qt.LeftButton) + + """ + Confirm if the user interface elements change accordingly + """ + assert scantest.keep_info() == __rtrn__ + + """ + Cleanup the temporary files from temp directory + """ + kill_temp_file() + remove(savefile.name) diff --git a/test/static/img/gi-loadouts-ocr-test.webp b/test/static/img/gi-loadouts-ocr-test.webp new file mode 100644 index 00000000..c7256fc7 Binary files /dev/null and b/test/static/img/gi-loadouts-ocr-test.webp differ