Latex tests
This commit is contained in:
parent
5211821a84
commit
4ac7de48b0
6 changed files with 139 additions and 39 deletions
|
@ -18,13 +18,12 @@
|
||||||
|
|
||||||
from PySide6.QtCore import QObject, Slot, Property, QCoreApplication
|
from PySide6.QtCore import QObject, Slot, Property, QCoreApplication
|
||||||
from PySide6.QtGui import QImage, QColor
|
from PySide6.QtGui import QImage, QColor
|
||||||
from PySide6.QtWidgets import QApplication, QMessageBox
|
from PySide6.QtWidgets import QMessageBox
|
||||||
|
|
||||||
from os import path, remove
|
from os import path, remove
|
||||||
from string import Template
|
from string import Template
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
from subprocess import Popen, TimeoutExpired, PIPE
|
from subprocess import Popen, TimeoutExpired, PIPE
|
||||||
from platform import system
|
|
||||||
from shutil import which
|
from shutil import which
|
||||||
from sys import argv
|
from sys import argv
|
||||||
|
|
||||||
|
@ -36,6 +35,7 @@ If not found, it will send an alert to the user.
|
||||||
LATEX_PATH = which('latex')
|
LATEX_PATH = which('latex')
|
||||||
DVIPNG_PATH = which('dvipng')
|
DVIPNG_PATH = which('dvipng')
|
||||||
PACKAGES = ["calligra", "amsfonts", "inputenc"]
|
PACKAGES = ["calligra", "amsfonts", "inputenc"]
|
||||||
|
SHOW_GUI_MESSAGES = "--test-build" not in argv
|
||||||
|
|
||||||
DEFAULT_LATEX_DOC = Template(r"""
|
DEFAULT_LATEX_DOC = Template(r"""
|
||||||
\documentclass[]{minimal}
|
\documentclass[]{minimal}
|
||||||
|
@ -54,6 +54,20 @@ $$$$ $markup $$$$
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
|
||||||
|
def show_message(msg: str) -> None:
|
||||||
|
"""
|
||||||
|
Shows a GUI message if GUI messages are enabled
|
||||||
|
"""
|
||||||
|
if SHOW_GUI_MESSAGES:
|
||||||
|
QMessageBox.warning(None, "LogarithmPlotter - Latex", msg)
|
||||||
|
|
||||||
|
|
||||||
|
class MissingPackageException(Exception): pass
|
||||||
|
|
||||||
|
|
||||||
|
class RenderError(Exception): pass
|
||||||
|
|
||||||
|
|
||||||
class Latex(QObject):
|
class Latex(QObject):
|
||||||
"""
|
"""
|
||||||
Base class to convert Latex equations into PNG images with custom font color and size.
|
Base class to convert Latex equations into PNG images with custom font color and size.
|
||||||
|
@ -77,22 +91,20 @@ class Latex(QObject):
|
||||||
valid_install = True
|
valid_install = True
|
||||||
if LATEX_PATH is None:
|
if LATEX_PATH is None:
|
||||||
print("No Latex installation found.")
|
print("No Latex installation found.")
|
||||||
if "--test-build" not in argv:
|
|
||||||
msg = QCoreApplication.translate("latex",
|
msg = QCoreApplication.translate("latex",
|
||||||
"No Latex installation found.\nIf you already have a latex distribution installed, make sure it's installed on your path.\nOtherwise, you can download a Latex distribution like TeX Live at https://tug.org/texlive/.")
|
"No Latex installation found.\nIf you already have a latex distribution installed, make sure it's installed on your path.\nOtherwise, you can download a Latex distribution like TeX Live at https://tug.org/texlive/.")
|
||||||
QMessageBox.warning(None, "LogarithmPlotter - Latex setup", msg)
|
show_message(msg)
|
||||||
valid_install = False
|
valid_install = False
|
||||||
elif DVIPNG_PATH is None:
|
elif DVIPNG_PATH is None:
|
||||||
print("DVIPNG not found.")
|
print("DVIPNG not found.")
|
||||||
if "--test-build" not in argv:
|
|
||||||
msg = QCoreApplication.translate("latex",
|
msg = QCoreApplication.translate("latex",
|
||||||
"DVIPNG was not found. Make sure you include it from your Latex distribution.")
|
"DVIPNG was not found. Make sure you include it from your Latex distribution.")
|
||||||
QMessageBox.warning(None, "LogarithmPlotter - Latex setup", msg)
|
show_message(msg)
|
||||||
valid_install = False
|
valid_install = False
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
self.render("", 14, QColor(0, 0, 0, 255))
|
self.render("", 14, QColor(0, 0, 0, 255))
|
||||||
except Exception as e:
|
except MissingPackageException:
|
||||||
valid_install = False # Should have sent an error message if failed to render
|
valid_install = False # Should have sent an error message if failed to render
|
||||||
return valid_install
|
return valid_install
|
||||||
|
|
||||||
|
@ -105,7 +117,6 @@ class Latex(QObject):
|
||||||
if self.latexSupported and not path.exists(export_path + ".png"):
|
if self.latexSupported and not path.exists(export_path + ".png"):
|
||||||
print("Rendering", latex_markup, export_path)
|
print("Rendering", latex_markup, export_path)
|
||||||
# Generating file
|
# Generating file
|
||||||
try:
|
|
||||||
latex_path = path.join(self.tempdir.name, str(markup_hash))
|
latex_path = path.join(self.tempdir.name, str(markup_hash))
|
||||||
# If the formula is just recolored or the font is just changed, no need to recreate the DVI.
|
# If the formula is just recolored or the font is just changed, no need to recreate the DVI.
|
||||||
if not path.exists(latex_path + ".dvi"):
|
if not path.exists(latex_path + ".dvi"):
|
||||||
|
@ -117,8 +128,6 @@ class Latex(QObject):
|
||||||
# self.convert_dvi_to_png(latex_path, export_path+"@2", font_size*2, color)
|
# self.convert_dvi_to_png(latex_path, export_path+"@2", font_size*2, color)
|
||||||
# self.convert_dvi_to_png(latex_path, export_path+"@3", font_size*3, color)
|
# self.convert_dvi_to_png(latex_path, export_path+"@3", font_size*3, color)
|
||||||
# self.convert_dvi_to_png(latex_path, export_path+"@4", font_size*4, color)
|
# self.convert_dvi_to_png(latex_path, export_path+"@4", font_size*4, color)
|
||||||
except Exception as e: # One of the processes failed. A message will be sent every time.
|
|
||||||
raise e
|
|
||||||
img = QImage(export_path)
|
img = QImage(export_path)
|
||||||
# Small hack, not very optimized since we load the image twice, but you can't pass a QImage to QML and expect it to be loaded
|
# Small hack, not very optimized since we load the image twice, but you can't pass a QImage to QML and expect it to be loaded
|
||||||
return f'{export_path}.png,{img.width()},{img.height()}'
|
return f'{export_path}.png,{img.width()},{img.height()}'
|
||||||
|
@ -147,7 +156,6 @@ class Latex(QObject):
|
||||||
"""
|
"""
|
||||||
Creates a temporary latex document with base file_hash as file name and a given expression markup latex_markup.
|
Creates a temporary latex document with base file_hash as file name and a given expression markup latex_markup.
|
||||||
"""
|
"""
|
||||||
ltx_path = export_path + ".tex"
|
|
||||||
f = open(export_path + ".tex", 'w')
|
f = open(export_path + ".tex", 'w')
|
||||||
f.write(DEFAULT_LATEX_DOC.substitute(markup=latex_markup))
|
f.write(DEFAULT_LATEX_DOC.substitute(markup=latex_markup))
|
||||||
f.close()
|
f.close()
|
||||||
|
@ -193,10 +201,10 @@ class Latex(QObject):
|
||||||
output = str(out, 'utf8') + "\n" + str(err, 'utf8')
|
output = str(out, 'utf8') + "\n" + str(err, 'utf8')
|
||||||
msg = QCoreApplication.translate("latex",
|
msg = QCoreApplication.translate("latex",
|
||||||
"An exception occured within the creation of the latex formula.\nProcess '{}' ended with a non-zero return code {}:\n\n{}\nPlease make sure your latex installation is correct and report a bug if so.")
|
"An exception occured within the creation of the latex formula.\nProcess '{}' ended with a non-zero return code {}:\n\n{}\nPlease make sure your latex installation is correct and report a bug if so.")
|
||||||
msg = msg.format(cmd, proc.returncode, output)
|
show_message(msg.format(cmd, proc.returncode, output))
|
||||||
QMessageBox.warning(None, "LogarithmPlotter - Latex", msg)
|
raise RenderError(
|
||||||
raise Exception(f"{cmd} process exited with return code {str(proc.returncode)}:\n{str(out, 'utf8')}\n{str(err, 'utf8')}")
|
f"{cmd} process exited with return code {str(proc.returncode)}:\n{str(out, 'utf8')}\n{str(err, 'utf8')}")
|
||||||
except TimeoutExpired as e:
|
except TimeoutExpired:
|
||||||
# Process timed out
|
# Process timed out
|
||||||
proc.kill()
|
proc.kill()
|
||||||
out, err = proc.communicate()
|
out, err = proc.communicate()
|
||||||
|
@ -207,12 +215,12 @@ class Latex(QObject):
|
||||||
# Package missing.
|
# Package missing.
|
||||||
msg = QCoreApplication.translate("latex",
|
msg = QCoreApplication.translate("latex",
|
||||||
"Your LaTeX installation does not include some required packages:\n\n- {} (https://ctan.org/pkg/{})\n\nMake sure said package is installed, or disable the LaTeX rendering in LogarithmPlotter.")
|
"Your LaTeX installation does not include some required packages:\n\n- {} (https://ctan.org/pkg/{})\n\nMake sure said package is installed, or disable the LaTeX rendering in LogarithmPlotter.")
|
||||||
QMessageBox.warning(None, "LogarithmPlotter - Latex", msg.format(pkg, pkg))
|
show_message(msg.format(pkg, pkg))
|
||||||
raise Exception("Latex: Missing package " + pkg)
|
raise MissingPackageException("Latex: Missing package " + pkg)
|
||||||
msg = QCoreApplication.translate("latex",
|
msg = QCoreApplication.translate("latex",
|
||||||
"An exception occured within the creation of the latex formula.\nProcess '{}' took too long to finish:\n{}\nPlease make sure your latex installation is correct and report a bug if so.")
|
"An exception occured within the creation of the latex formula.\nProcess '{}' took too long to finish:\n{}\nPlease make sure your latex installation is correct and report a bug if so.")
|
||||||
QMessageBox.warning(None, "LogarithmPlotter - Latex", msg.format(cmd, output))
|
show_message(msg.format(cmd, output))
|
||||||
raise Exception(f"{cmd} process timed out:\n{output}")
|
raise RenderError(f"{cmd} process timed out:\n{output}")
|
||||||
|
|
||||||
def cleanup(self, export_path):
|
def cleanup(self, export_path):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -14,6 +14,7 @@ steps:
|
||||||
- name: Tests
|
- name: Tests
|
||||||
image: ad5001/ubuntu-pyside6-xvfb:noble-6.7.2
|
image: ad5001/ubuntu-pyside6-xvfb:noble-6.7.2
|
||||||
commands:
|
commands:
|
||||||
|
- apt install -y texlive-base dvipng texlive-latex-extra # Install latex dependencies.
|
||||||
- pytest --cov --cov-report term-missing
|
- pytest --cov --cov-report term-missing
|
||||||
- xvfb-run python3 run.py --test-build --no-check-for-updates
|
- xvfb-run python3 run.py --test-build --no-check-for-updates
|
||||||
- xvfb-run python3 run.py --test-build --no-check-for-updates ./ci/test1.lpf
|
- xvfb-run python3 run.py --test-build --no-check-for-updates ./ci/test1.lpf
|
||||||
|
|
21
poetry.lock
generated
21
poetry.lock
generated
|
@ -315,6 +315,25 @@ pytest = ">=4.6"
|
||||||
[package.extras]
|
[package.extras]
|
||||||
testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"]
|
testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest-qt"
|
||||||
|
version = "4.4.0"
|
||||||
|
description = "pytest support for PyQt and PySide applications"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "pytest-qt-4.4.0.tar.gz", hash = "sha256:76896142a940a4285339008d6928a36d4be74afec7e634577e842c9cc5c56844"},
|
||||||
|
{file = "pytest_qt-4.4.0-py3-none-any.whl", hash = "sha256:001ed2f8641764b394cf286dc8a4203e40eaf9fff75bf0bfe5103f7f8d0c591d"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
pluggy = ">=1.1"
|
||||||
|
pytest = "*"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dev = ["pre-commit", "tox"]
|
||||||
|
doc = ["sphinx", "sphinx-rtd-theme"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pywin32-ctypes"
|
name = "pywin32-ctypes"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
|
@ -402,4 +421,4 @@ type = ["pytest-mypy"]
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = ">=3.9,<3.13"
|
python-versions = ">=3.9,<3.13"
|
||||||
content-hash = "8ce304f6a3fbab24428232c1a7d0b59ea412094e82d6b8ce47e4d93462cc235a"
|
content-hash = "4693a671e927103ceeb946f688b84fdc56b8b39b2cd772d8d32475e1236d8a07"
|
||||||
|
|
|
@ -15,8 +15,5 @@ PySide6-Essentials = "^6.7.2"
|
||||||
pyinstaller = "^6.10.0"
|
pyinstaller = "^6.10.0"
|
||||||
pytest = "^8.3.3"
|
pytest = "^8.3.3"
|
||||||
pytest-cov = "^5.0.0"
|
pytest-cov = "^5.0.0"
|
||||||
|
pytest-qt = "^4.4.0"
|
||||||
stdeb = "^0.10.0"
|
stdeb = "^0.10.0"
|
||||||
|
|
||||||
[build-system]
|
|
||||||
requires = ["poetry-core"]
|
|
||||||
build-backend = "poetry.core.masonry.api"
|
|
||||||
|
|
7
scripts/run-tests.sh
Normal file
7
scripts/run-tests.sh
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
#!/bin/bash
|
||||||
|
cd "$(dirname "$(readlink -f "$0" || realpath "$0")")/.."
|
||||||
|
|
||||||
|
# Run python tests
|
||||||
|
pytest --cov --cov-report term-missing
|
||||||
|
|
||||||
|
|
68
tests/python/test_latex.py
Normal file
68
tests/python/test_latex.py
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
import pytest
|
||||||
|
from tempfile import TemporaryDirectory
|
||||||
|
from shutil import which
|
||||||
|
from os.path import exists
|
||||||
|
from re import match
|
||||||
|
from PySide6.QtGui import QColor
|
||||||
|
|
||||||
|
from LogarithmPlotter.util import latex
|
||||||
|
|
||||||
|
latex.SHOW_GUI_MESSAGES = False
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def latex_obj():
|
||||||
|
directory = TemporaryDirectory()
|
||||||
|
obj = latex.Latex(directory)
|
||||||
|
if not obj.checkLatexInstallation():
|
||||||
|
raise Exception("Cannot run LaTeX tests without a proper LaTeX installation. Make sure to install a LaTeX distribution, DVIPNG, and the calligra package, and run the tests again.")
|
||||||
|
yield obj
|
||||||
|
directory.cleanup()
|
||||||
|
|
||||||
|
|
||||||
|
class TestLatex:
|
||||||
|
def test_check_install(self, latex_obj: latex.Latex) -> None:
|
||||||
|
assert latex_obj.latexSupported == True
|
||||||
|
assert latex_obj.checkLatexInstallation() == True
|
||||||
|
bkp = [latex.DVIPNG_PATH, latex.LATEX_PATH]
|
||||||
|
# Check what happens when one is missing.
|
||||||
|
latex.DVIPNG_PATH = None
|
||||||
|
assert latex_obj.latexSupported == False
|
||||||
|
assert latex_obj.checkLatexInstallation() == False
|
||||||
|
latex.DVIPNG_PATH = bkp[0]
|
||||||
|
latex.LATEX_PATH = None
|
||||||
|
assert latex_obj.latexSupported == False
|
||||||
|
assert latex_obj.checkLatexInstallation() == False
|
||||||
|
# Reset
|
||||||
|
[latex.DVIPNG_PATH, latex.LATEX_PATH] = bkp
|
||||||
|
|
||||||
|
def test_render(self, latex_obj: latex.Latex) -> None:
|
||||||
|
result = latex_obj.render(r"\frac{d\sqrt{\mathrm{f}(x \times 2.3)}}{dx}", 14, QColor(0, 0, 0, 255))
|
||||||
|
# Ensure result format
|
||||||
|
assert type(result) == str
|
||||||
|
[path, width, height] = result.split(",")
|
||||||
|
assert exists(path)
|
||||||
|
assert match(r"\d+", width)
|
||||||
|
assert match(r"\d+", height)
|
||||||
|
# Ensure it returns errors on invalid latex.
|
||||||
|
with pytest.raises(latex.RenderError):
|
||||||
|
latex_obj.render(r"\nonexistant", 14, QColor(0, 0, 0, 255))
|
||||||
|
# Replace latex bin with one that returns errors
|
||||||
|
bkp = latex.LATEX_PATH
|
||||||
|
latex.LATEX_PATH = which("false")
|
||||||
|
with pytest.raises(latex.RenderError):
|
||||||
|
latex_obj.render(r"\mathrm{f}(x)", 14, QColor(0, 0, 0, 255))
|
||||||
|
latex.LATEX_PATH = bkp
|
||||||
|
|
||||||
|
def test_prerendered(self, latex_obj: latex.Latex) -> None:
|
||||||
|
args = [r"\frac{d\sqrt{\mathrm{f}(x \times 2.3)}}{dx}", 14, QColor(0, 0, 0, 255)]
|
||||||
|
latex_obj.render(*args)
|
||||||
|
prerendered = latex_obj.findPrerendered(*args)
|
||||||
|
assert type(prerendered) == str
|
||||||
|
[path, width, height] = prerendered.split(",")
|
||||||
|
assert exists(path)
|
||||||
|
assert match(r"\d+", width)
|
||||||
|
assert match(r"\d+", height)
|
||||||
|
prerendered2 = latex_obj.findPrerendered(args[0], args[1]+2, args[2])
|
||||||
|
assert prerendered2 == ""
|
||||||
|
|
Loading…
Reference in a new issue