Compare commits

...
Author SHA1 Message Date
Kara Engelhardt 547de7aa26 pdf tests: Save temporary pdfs for failed test debugging 2026-09-18 16:49:29 +02:00
Kara Engelhardt 03bca84e00 Add pdf tests 2026-09-18 12:16:14 +02:00
10 changed files with 919 additions and 1 deletions
+1
View File
@@ -44,6 +44,7 @@ recursive-include src *.py
recursive-include src *.svg
recursive-include src *.txt
recursive-include src Makefile
recursive-include src *.pdf
recursive-exclude doc *
recursive-exclude deployment *
+1
View File
@@ -129,6 +129,7 @@ dev = [
"pytest==9.1.*",
"playwright",
"responses",
"pypdfium2"
]
[project.entry-points."distutils.commands"]
+23 -1
View File
@@ -21,6 +21,8 @@
#
import inspect
import os
import re
from pathlib import Path
import pytest
from django.core.cache import cache
@@ -28,7 +30,6 @@ from django.test import override_settings
from django.utils import translation
from django_scopes import scopes_disabled
from fakeredis import FakeRedisConnection
from hierarkey.proxy import dirty_cache_keys
from xdist.dsession import DSession
from pretix.testutils.mock import get_redis_connection
@@ -85,6 +86,7 @@ def reset_locale():
@pytest.fixture(autouse=True)
def reset_hierarkey_cache_state():
from hierarkey.proxy import dirty_cache_keys
dirty_cache_keys.set(set())
@@ -143,3 +145,23 @@ def set_lock_namespaces(request):
@pytest.fixture
def class_monkeypatch(request, monkeypatch):
request.cls.monkeypatch = monkeypatch
def pytest_addoption(parser):
parser.addoption(
'--pdf-dir', action='store', help="output dir for files generated by pdf tests", type=Path
)
parser.addoption(
'--pdf-tmp-keep', action='store_true', help="keep the temporary directory created for pdf tests"
)
@pytest.fixture
def pdf_dir(request, tmp_path_factory):
dirname = re.sub('[^a-zA-Z0-9]+', '_', request.node.name).strip("_")
if dir := request.config.getoption('--pdf-dir'):
pdf_dir: Path = (dir / dirname).absolute()
pdf_dir.mkdir(exist_ok=True, parents=True)
yield pdf_dir
else:
yield tmp_path_factory.mktemp(dirname)
@@ -0,0 +1,99 @@
%PDF-1.3
%âãÏÓ
1 0 obj
<<
/PageMode /UseNone
/Pages 2 0 R
/Type /Catalog
>>
endobj
2 0 obj
<<
/Count 1
/Kids [ 3 0 R ]
/Type /Pages
>>
endobj
3 0 obj
<<
/Contents 4 0 R
/MediaBox [ 0 0 595.2756 841.8898 ]
/Parent 2 0 R
/Resources <<
/Font 5 0 R
/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>>
/Rotate 0
/Trans <<
>>
/Type /Page
/CropBox [ 10 10 307.84 220 ]
>>
endobj
4 0 obj
<<
/Length 117
>>
stream
q
1 0.0 0.0 1 10 10 cm
1 0 0 1 0 0 cm
BT
/F1 12 Tf
14.4 TL
ET
1 0 0 rg
n
56.69291 56.69291 141.7323 28.34646 re
B*
Q
endstream
endobj
5 0 obj
<<
/F1 6 0 R
>>
endobj
6 0 obj
<<
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
/Name /F1
/Subtype /Type1
/Type /Font
>>
endobj
7 0 obj
<<
/Author (anonymous)
/CreationDate (D\07220260917173038\05302\04700\047)
/Creator (anonymous)
/Keywords ()
/ModDate (D\07220260917173038\05302\04700\047)
/Producer (ReportLab PDF Library \055 \050opensource\051)
/Subject (unspecified)
/Title (untitled)
/Trapped /False
>>
endobj
xref
0 8
0000000000 65535 f
0000000015 00000 n
0000000083 00000 n
0000000142 00000 n
0000000371 00000 n
0000000539 00000 n
0000000570 00000 n
0000000677 00000 n
trailer
<<
/Size 8
/Root 1 0 R
/Info 7 0 R
/ID [ <a3fd776687419b9853cf896bf5d6dc96> <a3fd776687419b9853cf896bf5d6dc96> ]
>>
startxref
966
%%EOF
@@ -0,0 +1,213 @@
%PDF-1.3
%âãÏÓ
1 0 obj
<<
/Producer (pypdf)
>>
endobj
2 0 obj
<<
/Type /Pages
/Count 2
/Kids [ 4 0 R 11 0 R ]
>>
endobj
3 0 obj
<<
/Type /Catalog
/Pages 2 0 R
>>
endobj
4 0 obj
<<
/Contents 5 0 R
/MediaBox [ 0 0 297.84 210 ]
/Resources <<
/Font 6 0 R
/ProcSet [ /ImageB /ImageC /ImageI /PDF /Text ]
>>
/Rotate 0
/Trans <<
>>
/Type /Page
/Parent 2 0 R
>>
endobj
5 0 obj
[ 9 0 R 10 0 R ]
endobj
6 0 obj
<<
/F1 7 0 R
/F1-0 8 0 R
>>
endobj
7 0 obj
<<
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
/Name /F1
/Subtype /Type1
/Type /Font
>>
endobj
8 0 obj
<<
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
/Name /F1
/Subtype /Type1
/Type /Font
>>
endobj
9 0 obj
<<
/Length 170
>>
stream
q
1 0.0 0.0 1 -10 -10 cm
10 10 297.84 210 re
W
n
q
1 0.0 0.0 1 10 10 cm
1 0 0 1 0 0 cm
BT
/F1-0 12 Tf
14.4 TL
ET
1 0 0 rg
n
56.69291 56.69291 141.7323 28.34646 re
B*
Q
Q
endstream
endobj
10 0 obj
<<
/Length 106
>>
stream
q
1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET
q
1 0 0 1 37.10551 140.9669 cm
q
1 0 0 1 0 5.669531 cm
Q
Q
Q
endstream
endobj
11 0 obj
<<
/Contents 12 0 R
/MediaBox [ 0 0 297.84 210 ]
/Resources <<
/Font 13 0 R
/ProcSet [ /ImageB /ImageC /ImageI /PDF /Text ]
>>
/Rotate 0
/Trans <<
>>
/Type /Page
/Parent 2 0 R
>>
endobj
12 0 obj
[ 15 0 R 16 0 R ]
endobj
13 0 obj
<<
/F1 14 0 R
/F1-0 8 0 R
>>
endobj
14 0 obj
<<
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
/Name /F1
/Subtype /Type1
/Type /Font
>>
endobj
15 0 obj
<<
/Length 170
>>
stream
q
1 0.0 0.0 1 -10 -10 cm
10 10 297.84 210 re
W
n
q
1 0.0 0.0 1 10 10 cm
1 0 0 1 0 0 cm
BT
/F1-0 12 Tf
14.4 TL
ET
1 0 0 rg
n
56.69291 56.69291 141.7323 28.34646 re
B*
Q
Q
endstream
endobj
16 0 obj
<<
/Length 106
>>
stream
q
1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET
q
1 0 0 1 37.10551 140.9669 cm
q
1 0 0 1 0 5.669531 cm
Q
Q
Q
endstream
endobj
xref
0 17
0000000000 65535 f
0000000015 00000 n
0000000054 00000 n
0000000120 00000 n
0000000169 00000 n
0000000361 00000 n
0000000393 00000 n
0000000436 00000 n
0000000543 00000 n
0000000650 00000 n
0000000871 00000 n
0000001029 00000 n
0000001224 00000 n
0000001258 00000 n
0000001303 00000 n
0000001411 00000 n
0000001633 00000 n
trailer
<<
/Size 17
/Root 3 0 R
/Info 1 0 R
>>
startxref
1791
%%EOF
@@ -0,0 +1,98 @@
%PDF-1.3
%âãÏÓ
1 0 obj
<<
/PageMode /UseNone
/Pages 2 0 R
/Type /Catalog
>>
endobj
2 0 obj
<<
/Count 1
/Kids [ 3 0 R ]
/Type /Pages
>>
endobj
3 0 obj
<<
/Contents 4 0 R
/MediaBox [ 10 10 307.84 220 ]
/Parent 2 0 R
/Resources <<
/Font 5 0 R
/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
>>
/Rotate 0
/Trans <<
>>
/Type /Page
>>
endobj
4 0 obj
<<
/Length 117
>>
stream
q
1 0.0 0.0 1 10 10 cm
1 0 0 1 0 0 cm
BT
/F1 12 Tf
14.4 TL
ET
1 0 0 rg
n
56.69291 56.69291 141.7323 28.34646 re
B*
Q
endstream
endobj
5 0 obj
<<
/F1 6 0 R
>>
endobj
6 0 obj
<<
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
/Name /F1
/Subtype /Type1
/Type /Font
>>
endobj
7 0 obj
<<
/Author (anonymous)
/CreationDate (D\07220260917173038\05302\04700\047)
/Creator (anonymous)
/Keywords ()
/ModDate (D\07220260917173038\05302\04700\047)
/Producer (ReportLab PDF Library \055 \050opensource\051)
/Subject (unspecified)
/Title (untitled)
/Trapped /False
>>
endobj
xref
0 8
0000000000 65535 f
0000000015 00000 n
0000000083 00000 n
0000000142 00000 n
0000000336 00000 n
0000000504 00000 n
0000000535 00000 n
0000000642 00000 n
trailer
<<
/Size 8
/Root 1 0 R
/Info 7 0 R
/ID [ <a3fd776687419b9853cf896bf5d6dc96> <a3fd776687419b9853cf896bf5d6dc96> ]
>>
startxref
931
%%EOF
@@ -0,0 +1,213 @@
%PDF-1.3
%âãÏÓ
1 0 obj
<<
/Producer (pypdf)
>>
endobj
2 0 obj
<<
/Type /Pages
/Count 2
/Kids [ 4 0 R 11 0 R ]
>>
endobj
3 0 obj
<<
/Type /Catalog
/Pages 2 0 R
>>
endobj
4 0 obj
<<
/Contents 5 0 R
/MediaBox [ 0 0 297.84 210 ]
/Resources <<
/Font 6 0 R
/ProcSet [ /ImageB /ImageC /ImageI /PDF /Text ]
>>
/Rotate 0
/Trans <<
>>
/Type /Page
/Parent 2 0 R
>>
endobj
5 0 obj
[ 9 0 R 10 0 R ]
endobj
6 0 obj
<<
/F1 7 0 R
/F1-0 8 0 R
>>
endobj
7 0 obj
<<
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
/Name /F1
/Subtype /Type1
/Type /Font
>>
endobj
8 0 obj
<<
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
/Name /F1
/Subtype /Type1
/Type /Font
>>
endobj
9 0 obj
<<
/Length 170
>>
stream
q
1 0.0 0.0 1 -10 -10 cm
10 10 297.84 210 re
W
n
q
1 0.0 0.0 1 10 10 cm
1 0 0 1 0 0 cm
BT
/F1-0 12 Tf
14.4 TL
ET
1 0 0 rg
n
56.69291 56.69291 141.7323 28.34646 re
B*
Q
Q
endstream
endobj
10 0 obj
<<
/Length 106
>>
stream
q
1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET
q
1 0 0 1 37.10551 140.9669 cm
q
1 0 0 1 0 5.669531 cm
Q
Q
Q
endstream
endobj
11 0 obj
<<
/Contents 12 0 R
/MediaBox [ 0 0 297.84 210 ]
/Resources <<
/Font 13 0 R
/ProcSet [ /ImageB /ImageC /ImageI /PDF /Text ]
>>
/Rotate 0
/Trans <<
>>
/Type /Page
/Parent 2 0 R
>>
endobj
12 0 obj
[ 15 0 R 16 0 R ]
endobj
13 0 obj
<<
/F1 14 0 R
/F1-0 8 0 R
>>
endobj
14 0 obj
<<
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
/Name /F1
/Subtype /Type1
/Type /Font
>>
endobj
15 0 obj
<<
/Length 170
>>
stream
q
1 0.0 0.0 1 -10 -10 cm
10 10 297.84 210 re
W
n
q
1 0.0 0.0 1 10 10 cm
1 0 0 1 0 0 cm
BT
/F1-0 12 Tf
14.4 TL
ET
1 0 0 rg
n
56.69291 56.69291 141.7323 28.34646 re
B*
Q
Q
endstream
endobj
16 0 obj
<<
/Length 106
>>
stream
q
1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET
q
1 0 0 1 37.10551 140.9669 cm
q
1 0 0 1 0 5.669531 cm
Q
Q
Q
endstream
endobj
xref
0 17
0000000000 65535 f
0000000015 00000 n
0000000054 00000 n
0000000120 00000 n
0000000169 00000 n
0000000361 00000 n
0000000393 00000 n
0000000436 00000 n
0000000543 00000 n
0000000650 00000 n
0000000871 00000 n
0000001029 00000 n
0000001224 00000 n
0000001258 00000 n
0000001303 00000 n
0000001411 00000 n
0000001633 00000 n
trailer
<<
/Size 17
/Root 3 0 R
/Info 1 0 R
>>
startxref
1791
%%EOF
Binary file not shown.
@@ -0,0 +1,216 @@
%PDF-1.7
%âãÏÓ
1 0 obj
<<
/Producer (pypdf)
>>
endobj
2 0 obj
<<
/Type /Pages
/Count 2
/Kids [ 4 0 R 11 0 R ]
>>
endobj
3 0 obj
<<
/Type /Catalog
/Pages 2 0 R
>>
endobj
4 0 obj
<<
/Contents 5 0 R
/MediaBox [ 0 0 210 297.84 ]
/Resources <<
/Font 6 0 R
/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
/ExtGState <<
/GS0 8 0 R
>>
>>
/Rotate 0
/Trans <<
>>
/Type /Page
/Parent 2 0 R
>>
endobj
5 0 obj
[ 9 0 R 10 0 R ]
endobj
6 0 obj
<<
/F1 7 0 R
>>
endobj
7 0 obj
<<
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
/Name /F1
/Subtype /Type1
/Type /Font
>>
endobj
8 0 obj
<<
/Type /ExtGState
/OP false
/op false
/OPM 1
>>
endobj
9 0 obj
<<
/Length 287
>>
stream
q
0.00000000000000006123234 -1 1 0.00000000000000006123234 0.0 297.84 cm
0.0 0.0 297.84 210 re
W
n
/GS0 gs
0.925811 0.881331 0.538643 rg
0 0.23622 297.638 209.764 re
f*
0.476631 0.756542 0.260899 rg
0 0.23622 297.638 209.764 re
292.638 205 m
5 205 l
5 5.23622 l
292.638 5.23622 l
h
f*
Q
endstream
endobj
10 0 obj
<<
/Length 106
>>
stream
q
1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET
q
1 0 0 1 37.10551 140.9669 cm
q
1 0 0 1 0 5.669531 cm
Q
Q
Q
endstream
endobj
11 0 obj
<<
/Contents 12 0 R
/MediaBox [ 0 0 210 297.84 ]
/Resources <<
/Font 13 0 R
/ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
/ExtGState <<
/GS0 8 0 R
>>
>>
/Rotate 0
/Trans <<
>>
/Type /Page
/Parent 2 0 R
>>
endobj
12 0 obj
[ 15 0 R 16 0 R ]
endobj
13 0 obj
<<
/F1 14 0 R
>>
endobj
14 0 obj
<<
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
/Name /F1
/Subtype /Type1
/Type /Font
>>
endobj
15 0 obj
<<
/Length 287
>>
stream
q
0.00000000000000006123234 -1 1 0.00000000000000006123234 0.0 297.84 cm
0.0 0.0 297.84 210 re
W
n
/GS0 gs
0.925811 0.881331 0.538643 rg
0 0.23622 297.638 209.764 re
f*
0.476631 0.756542 0.260899 rg
0 0.23622 297.638 209.764 re
292.638 205 m
5 205 l
5 5.23622 l
292.638 5.23622 l
h
f*
Q
endstream
endobj
16 0 obj
<<
/Length 106
>>
stream
q
1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET
q
1 0 0 1 37.10551 140.9669 cm
q
1 0 0 1 0 5.669531 cm
Q
Q
Q
endstream
endobj
xref
0 17
0000000000 65535 f
0000000015 00000 n
0000000054 00000 n
0000000120 00000 n
0000000169 00000 n
0000000389 00000 n
0000000421 00000 n
0000000452 00000 n
0000000559 00000 n
0000000624 00000 n
0000000962 00000 n
0000001120 00000 n
0000001343 00000 n
0000001377 00000 n
0000001410 00000 n
0000001518 00000 n
0000001857 00000 n
trailer
<<
/Size 17
/Root 3 0 R
/Info 1 0 R
>>
startxref
2015
%%EOF
+55
View File
@@ -32,13 +32,18 @@
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under the License.
import logging
from datetime import timedelta
from decimal import Decimal
from io import BytesIO
from pathlib import Path
import pypdfium2
import pytest
from django.core.files import File
from django.utils.timezone import now
from django_scopes import scope
from PIL import ImageChops
from pypdf import PdfReader
from pretix.base.models import (
@@ -118,3 +123,53 @@ def test_generate_pdf_multi(env):
assert ftype == 'application/pdf'
pdf = PdfReader(BytesIO(buf))
assert len(pdf.pages) == 1
def asset_path(name):
return Path(__file__).parent / "assets" / name
def compare_pdfs(pdf_dir: Path, inp_a: Path | bytes, inp_b: Path | bytes):
logging.info(f"Comparing pdfs, writing files to {pdf_dir}")
pdf_a = pypdfium2.PdfDocument(inp_a)
pdf_b = pypdfium2.PdfDocument(inp_b)
pdf_a.save(pdf_dir / "a.pdf")
pdf_a.save(pdf_dir / "b.pdf")
assert len(pdf_a) == len(pdf_b)
for i, (expected_page, output_page) in enumerate(zip(pdf_a, pdf_b)):
expected_render = expected_page.render()
output_render = output_page.render()
assert expected_render.height == output_render.height
assert expected_render.width == output_render.width
diff = ImageChops.difference(expected_render.to_pil(), output_render.to_pil())
if diff.getbbox():
diff.save(pdf_dir / f"{i}.png")
assert not diff.getbbox(), f"Page {i} differs."
@pytest.mark.django_db
@pytest.mark.parametrize("case", [
"bg-rotated",
"bg-mediabox-offset",
"bg-cropbox"
])
def test_generate_pdf_weird_bgs(pdf_dir, env, case):
event, order, shirt = env
asset_folder = asset_path(case)
with open(asset_folder / "bg.pdf", 'rb') as fi:
event.badge_layouts.create(name="Default", default=True, background=File(fi, name="test.pdf"))
e = BadgeExporter(event, organizer=event.organizer)
fname, ftype, buf = e.render({
'items': [shirt.pk],
'rendering': 'one',
'include_pending': True
})
assert ftype == 'application/pdf'
assert buf
compare_pdfs(pdf_dir, buf, asset_folder / "expected.pdf")