mirror of
https://github.com/ARUP-CAS/aiscr-qgis-amcr-viewer.git
synced 2026-10-09 12:27:36 +02:00
tests/check_sources.py hlídá pravidla, která nepotřebují QGIS a jejichž porušení je tiché: - UTF-8 BOM na začátku .py souboru. Pythonu ani QGISu nevadí, ale oficiální pyqgis4-checker na něm spadne a soubor vůbec nezkontroluje, takže rozbitý soubor vypadá čistě. Stalo se tady už dvakrát. - přímý import z PyQt5/PyQt6 mimo shim qgis.PyQt - spustitelná práva, skryté soubory a podezřelé typy souborů, které hlásí analýza souborů na plugins.qgis.org tests/smoke_test.py načte plugin ve skutečném QGIS, přečte scoped enumy, vytvoří UpdateCodelistsTask a všechny tři filtrační dialogy a ověří doplňování hraničního data. Běží offline a bez X serveru (QT_QPA_PLATFORM=offscreen), takže nezávisí na dostupnosti API. Očekávaná hodnota filtru je v testu napsaná natvrdo, ne přes DATE_OPEN_TO. Porovnání proti konstantě z modulu dokazuje jen to, že se modul shodne sám se sebou, a prošlo i s rozbitým sentinelem. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WhQyc1uQFcpkezLwM8if6T
131 lines
3.8 KiB
Python
131 lines
3.8 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
Smoke test: loads the plugin inside a real QGIS and exercises the parts
|
||
that differ between Qt5 and Qt6.
|
||
|
||
It is deliberately offline – no request ever leaves the machine, so the
|
||
test says nothing about the AMCR API, only about the plugin loading and
|
||
its widgets being constructible.
|
||
|
||
Run it from the repository root:
|
||
|
||
python3 tests/smoke_test.py
|
||
|
||
QGIS must be importable (inside the qgis/qgis Docker image it already is).
|
||
The exit code is 0 when everything passed, 1 otherwise.
|
||
"""
|
||
|
||
import os
|
||
import sys
|
||
import traceback
|
||
|
||
# Offscreen, otherwise the dialogs need an X server
|
||
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
||
|
||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||
sys.path.insert(0, ROOT)
|
||
|
||
selhani = []
|
||
|
||
|
||
def zkouska(nazev, funkce):
|
||
"""Runs one check and keeps going even when it raises."""
|
||
try:
|
||
detail = funkce()
|
||
except Exception:
|
||
selhani.append(nazev)
|
||
print(f" FAIL {nazev}")
|
||
print(traceback.format_exc().rstrip())
|
||
else:
|
||
print(f" OK {nazev}" + (f" – {detail}" if detail else ""))
|
||
|
||
|
||
from qgis.core import ( # noqa: E402
|
||
Qgis,
|
||
QgsApplication,
|
||
QgsTask,
|
||
QgsWkbTypes,
|
||
)
|
||
from qgis.PyQt import QtCore # noqa: E402
|
||
from qgis.PyQt.QtCore import QDate # noqa: E402
|
||
|
||
print(f"QGIS {Qgis.QGIS_VERSION.split('-')[0]} | Qt {QtCore.QT_VERSION_STR} "
|
||
f"| PyQt {QtCore.PYQT_VERSION_STR}")
|
||
|
||
QgsApplication.setPrefixPath(os.environ.get("QGIS_PREFIX_PATH", "/usr"), True)
|
||
qgs = QgsApplication([], True)
|
||
qgs.initQgis()
|
||
|
||
import amcr_viewer.amcr_codelists # noqa: E402,F401
|
||
import amcr_viewer.amcr_dialog as dialog # noqa: E402
|
||
import amcr_viewer.amcr_tools # noqa: E402,F401
|
||
import amcr_viewer.amcr_viewer # noqa: E402,F401
|
||
|
||
print(" OK import všech modulů pluginu")
|
||
|
||
|
||
def enumy():
|
||
"""
|
||
The scoped enum forms must exist. Unscoped aliases still resolve in
|
||
QGIS 4.2, so a plain import proves nothing – these are read explicitly.
|
||
"""
|
||
return (f"QgsTask.Flag.CanCancel={int(QgsTask.Flag.CanCancel)}, "
|
||
f"PointGeometry={int(QgsWkbTypes.GeometryType.PointGeometry)}, "
|
||
f"MessageLevel.Info={int(Qgis.MessageLevel.Info)}")
|
||
|
||
|
||
def uloha():
|
||
ukol = dialog.UpdateCodelistsTask("smoke")
|
||
assert ukol.canCancel() is True
|
||
return "canCancel=True"
|
||
|
||
|
||
def dialogy():
|
||
# A modal warning would block the offscreen run forever
|
||
dialog.QMessageBox.warning = staticmethod(lambda *a, **k: None)
|
||
popis = []
|
||
for typ in ("akce", "lokalita", "samostatny_nalez"):
|
||
okno = dialog.AmcrFilterDialog(typ)
|
||
okno.show()
|
||
QgsApplication.processEvents()
|
||
popis.append(f"{typ}: {len(okno.date_ranges)} rozmezí")
|
||
okno.close()
|
||
return ", ".join(popis)
|
||
|
||
|
||
def filtr_datumu():
|
||
"""
|
||
A half-filled range must be completed with the sentinel. The API
|
||
rejects a one-sided range, so this is the part worth guarding.
|
||
|
||
The expected value is written out on purpose – comparing against
|
||
dialog.DATE_OPEN_TO would only prove the module agrees with itself.
|
||
"""
|
||
okno = dialog.AmcrFilterDialog("samostatny_nalez")
|
||
pole, _, od, _do = okno.date_ranges[0]
|
||
od.setDate(QDate(2016, 1, 1))
|
||
hodnota = okno.get_filters()[pole]
|
||
assert hodnota == "2016-01-01,9999-12-31", hodnota
|
||
|
||
# A range left completely empty must add no filter at all
|
||
prazdne = dialog.AmcrFilterDialog("samostatny_nalez")
|
||
pole_prazdne = prazdne.date_ranges[0][0]
|
||
assert pole_prazdne not in prazdne.get_filters()
|
||
prazdne.close()
|
||
|
||
okno.close()
|
||
return hodnota
|
||
|
||
|
||
zkouska("scoped enumy", enumy)
|
||
zkouska("UpdateCodelistsTask", uloha)
|
||
zkouska("filtrační dialogy", dialogy)
|
||
zkouska("filtr podle data", filtr_datumu)
|
||
|
||
qgs.exitQgis()
|
||
|
||
if selhani:
|
||
print(f"\nNEPROŠLO: {', '.join(selhani)}")
|
||
sys.exit(1)
|
||
print("\nVše prošlo")
|