From da8693cc257650cfe87dc8ea3894b9643c2ac5f2 Mon Sep 17 00:00:00 2001 From: utensil Date: Tue, 9 Apr 2024 14:58:26 +0800 Subject: [PATCH] Make gxpdf more testable and setup PDF diff tests --- .github/workflows/ci.yml | 19 +++++++++ .gitignore | 3 ++ galgebra/gprinter.py | 30 +++++++------ test/fixtures/test_gprinter.pdf | Bin 0 -> 10063 bytes test/test_gprinter.py | 73 ++++++++++++++++++++++++++++++++ 5 files changed, 113 insertions(+), 12 deletions(-) create mode 100644 test/fixtures/test_gprinter.pdf create mode 100644 test/test_gprinter.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b34bfaa4..f5d0276f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,25 @@ jobs: - name: Lint run: | flake8 -v + - name: Install apt deps with cache + uses: awalsh128/cache-apt-pkgs-action@latest + if: "matrix.python-version == '3.11'" + with: + packages: diff-pdf + version: 1.0 + - uses: actions/cache@v3 + name: Tectonic Cache + if: "matrix.python-version == '3.11'" + with: + path: ~/.cache/Tectonic + key: ${{ runner.os }}-tectonic-${{ hashFiles('**/*.tex') }} + restore-keys: | + ${{ runner.os }}-tectonic- + - uses: wtfjoke/setup-tectonic@v2 + if: "matrix.python-version == '3.11'" + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + biber-version: "latest" - name: Test run: | PYTEST_ARGS=(); diff --git a/.gitignore b/.gitignore index 9b724e36..172a254a 100644 --- a/.gitignore +++ b/.gitignore @@ -139,3 +139,6 @@ examples/**/*.tex # Mac .DS_Store + +test/generated +test/diff diff --git a/galgebra/gprinter.py b/galgebra/gprinter.py index 6c76477d..b65a9bcf 100644 --- a/galgebra/gprinter.py +++ b/galgebra/gprinter.py @@ -26,7 +26,7 @@ class LaTeX: latex_preamble = """ \\pagestyle{empty} -\\usepackage[latin1]{inputenc} +\\usepackage[utf8]{inputenc} \\usepackage{amsmath} \\usepackage{amsfonts} \\usepackage{amssymb} @@ -196,7 +196,7 @@ def gprint(*xargs): return -def gxpdf(filename=None, paper=(14, 11), crop=False, png=False, prog=False, debug=False, pt='10pt', pdfprog='pdflatex'): +def gxpdf(filename=None, paper=(14, 11), crop=False, png=False, prog=False, debug=False, pt='10pt', pdfprog='pdflatex', evince=True, rm=True, null=True): """ Post processes LaTeX output (see comments below), adds preamble and @@ -208,10 +208,11 @@ def gxpdf(filename=None, paper=(14, 11), crop=False, png=False, prog=False, debu crop True Use "pdfcrop" to crop output file (pdfcrop must be installed, linux only) png True Use "convert" to produce png output (imagemagick must be installed, linux only) - We assume that if xpdf() is called then Format() has been called at the beginning of the program. + We assume that if gxpdf() is called then gFormat() has been called at the beginning of the program. """ latex_str = paper_format(paper, pt)+LaTeX.latex_preamble+LaTeX.latex_str+r'\end{document}' + if filename is None: pyfilename = sys.argv[0] @@ -221,13 +222,13 @@ def gxpdf(filename=None, paper=(14, 11), crop=False, png=False, prog=False, debu else: tex_filename = filename pdf_filename = tex_filename.replace('.tex', '.pdf') + rootfilename = tex_filename.replace('.tex', '') if debug: print('latex file =', filename) - latex_file = open(tex_filename, 'w') - latex_file.write(latex_str) - latex_file.close() + with open(tex_filename, 'w') as latex_file: + latex_file.write(latex_str) if pdfprog is not None: sys_cmd = SYS_CMD[sys.platform] @@ -237,17 +238,22 @@ def gxpdf(filename=None, paper=(14, 11), crop=False, png=False, prog=False, debu print('pdflatex path =', pdflatex) # os.system(pdfprog + ' ' + filename[:-4]) else: # Works for Linux don't know about Windows - subprocess.call([pdfprog, tex_filename, sys_cmd['null']]) + if null: + subprocess.call([pdfprog, tex_filename, sys_cmd['null']]) + else: + subprocess.call([pdfprog, tex_filename]) # os.system(pdfprog + ' ' + filename[:-4] + sys_cmd['null']) - subprocess.call([sys_cmd['evince'], pdf_filename]) + if evince: + subprocess.call([sys_cmd['evince'], pdf_filename]) # eval(input('!!!!Return to continue!!!!\n')) - if debug: - subprocess.call([sys_cmd['rm'], rootfilename+'.aux ', rootfilename+'.log']) - else: - subprocess.call([sys_cmd['rm'], rootfilename+'.aux ', rootfilename+'.log ', rootfilename+'.tex']) + if rm: + if debug: + subprocess.call([sys_cmd['rm'], rootfilename+'.aux ', rootfilename+'.log']) + else: + subprocess.call([sys_cmd['rm'], rootfilename+'.aux ', rootfilename+'.log ', rootfilename+'.tex']) if crop: subprocess.call(['pdfcrop', pdf_filename]) subprocess.call(['rm', pdf_filename]) diff --git a/test/fixtures/test_gprinter.pdf b/test/fixtures/test_gprinter.pdf new file mode 100644 index 0000000000000000000000000000000000000000..203f96ae1018c12178b366108a3ce855367bac81 GIT binary patch literal 10063 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 If097^KRnRf(EtDd literal 0 HcmV?d00001 diff --git a/test/test_gprinter.py b/test/test_gprinter.py new file mode 100644 index 00000000..6e30a755 --- /dev/null +++ b/test/test_gprinter.py @@ -0,0 +1,73 @@ +from __future__ import annotations + +import os +import unittest +import pytest +import subprocess +from pathlib import Path + +# Make SymPy available to this program: +import sympy +from sympy import symbols + +# Make GAlgebra available to this program: +from galgebra.ga import Ga +# from galgebra.mv import * +from galgebra.printer import Fmt, GaPrinter, Format +# Fmt: sets the way that a multivector's basis expansion is output. +# GaPrinter: makes GA output a little more readable. +# Format: turns on latex printer. +from galgebra.gprinter import gFormat, gprint, gxpdf + +ROOT = Path(__file__).parent.parent +DIR_TEST = ROOT / 'test' +DIR_GENERATED = DIR_TEST / 'generated' +DIR_FIXTURES = DIR_TEST / 'fixtures' +DIR_DIFF = DIR_TEST / 'diff' + + +class TestGprinter(unittest.TestCase): + # @pytest.mark.skipif("TEST_GXPDF" not in os.environ, reason="Only run if TEST_GXPDF is set") + def test_gxpdf(self): + gFormat() + # Set up standard G^3 geometric algebra + g3coords = (x, y, z) = symbols('x y z', real=True) # Without real=True, symbols are complex + g3 = Ga(r'\mathbf{e}', g=[1, 1, 1], coords=g3coords) + (ex, ey, ez) = g3.mv() # Program names of basis vectors. + (exr, eyr, ezr) = g3.mvr() # Program names of reciprocal basis vectors. + + B = g3.mv('B', 'bivector') + Fmt(1) # Set Fmt globally + gprint(r'\mathbf{B} =', B) # B will be bold. + gprint(r'\mathbf{B} =', B.Fmt(3)) # Fmt(3) here only. + gprint(r'\mathbf{B} =', B) # Global Fmt remembered. + + gprint(r'\mathbf{B}^2 =', B*B) + + M = g3.mv('M', 'mv') + gprint(r'\langle \mathbf{M} \rangle_2 =', M.grade(2)) + # grade(2) could be replaced by, e.g., odd(), or omitted altogether. + + gprint(r'\alpha_1\mathbf{X}/\gamma_r^3') + + grad = g3.grad + + gprint(r'{\nabla} = ', grad) + + # mkdir -p + os.makedirs(DIR_GENERATED, exist_ok=True) + os.makedirs(DIR_DIFF, exist_ok=True) + os.chdir(DIR_GENERATED) + + gxpdf('test_gprinter.tex', pdfprog='tectonic', rm=False, null=False, evince=False) + + os.chdir(ROOT) + retcode = subprocess.call([ + 'diff-pdf', + '--output-diff=%s' % (DIR_DIFF / 'test_gprinter-diff.pdf'), + DIR_FIXTURES / 'test_gprinter.pdf', + DIR_GENERATED / 'test_gprinter.pdf' + ]) + assert retcode == 0 + + # os.system('pdf2svg test_gprinter.pdf test_gprinter.svg')