Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Used qpdf in all tests to compare resulting PDFs + improved development doc #33

Merged
merged 14 commits into from
Jan 8, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions docs/Development.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,22 +64,30 @@ in `.git/hooks/pre-commit` in order to always invoke `black` & `pylint`
before every commit:

```shell
#!/bin/sh
modified_py_files=$(git diff --cached --name-only --diff-filter=ACM | grep '.py$')
#!/bin/bash
git_cached_names() { git diff --cached --name-only --diff-filter=ACM; }
modified_py_files=$(git_cached_names | grep '.py$')
modified_fpdf_files=$(git_cached_names | grep ^fpdf | grep '.py$')
# if python files modified, format
if [ -n "$modified_py_files" ]; then
if ! black --check $modified_py_files; then
black $modified_py_files
exit 1
fi
pylint $modified_py_files
# if core files modified, lint
[[ $modified_fpdf_files == "" ]] || pylint $modified_fpdf_files
fi
unset git_cached_names
Lucas-C marked this conversation as resolved.
Show resolved Hide resolved
```

It will abort the commit if `pylint` found issues
or `black` detect non-properly formatted code.
In the later case though, it will auto-format your code
and you will just have to run `git commit -a` again.

Be sure to check on what actions will actually be run in the CI workflow in
Lucas-C marked this conversation as resolved.
Show resolved Hide resolved
`.github/workflows/continuous-integration-workflow.yml`.

## Testing ##

Testing is done with [Tox](https://tox.readthedocs.io/en/latest/), and is
Expand Down
1 change: 1 addition & 0 deletions test/cell_multi_cell_refactor/cell.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,6 @@ def test_ln_positioning_and_page_breaking_for_cell(self):
self, doc, "test_ln_positioning_and_page_breaking_for_cell.pdf"
)


if __name__ == "__main__":
unittest.main()
1 change: 1 addition & 0 deletions test/cell_multi_cell_refactor/multi_cell.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,6 @@ def test_ln_positioning_and_page_breaking_for_multicell(self):
self, doc, "test_ln_positioning_and_page_breaking_for_multicell.pdf"
)


if __name__ == "__main__":
unittest.main()
67 changes: 62 additions & 5 deletions test/utilities.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import hashlib
import inspect
import os
import pathlib
import shutil
import sys
import tempfile
import warnings
from datetime import datetime
from subprocess import check_call, check_output
from tempfile import NamedTemporaryFile

from fpdf.template import Template

Expand All @@ -18,6 +19,62 @@
)


class TempFile:
"""docstring for TempFile"""

def __init__(self, name, handle):
super(TempFile, self).__init__()
self.name = name
self.handle = handle

def read_whole_file(self):
while True:
if not self.handle.read(10):
Lucas-C marked this conversation as resolved.
Show resolved Hide resolved
break
return self.handle.read()

def rwf(self):
return self.read_whole_file()

def close(self):
self.handle.close()


class TempFileCtx:
"""docstring for TempFileCtxs"""
Lucas-C marked this conversation as resolved.
Show resolved Hide resolved

def __init__(self, prefix, delete, suffix):
super(TempFileCtx, self).__init__()
self.prefix = prefix
self.delete = delete
self.suffix = suffix

def random(self):
Lucas-C marked this conversation as resolved.
Show resolved Hide resolved
return os.urandom(24).hex()

def get_os_tmp(self):
return tempfile.gettempdir()

def get_tmp_file_name_base(self):
return self.prefix + self.random() + self.suffix

def get_tmp_file_name(self):
name = self.get_tmp_file_name_base()
return os.path.join(self.get_os_tmp(), name)

def __enter__(self):
self.abs_name = self.get_tmp_file_name()
pathlib.Path(self.abs_name).touch()
tmp_file = TempFile(self.abs_name, open(self.abs_name, "rb"))
self.tmp_file = tmp_file
return tmp_file

def __exit__(self, type, value, traceback):
self.tmp_file.close()
if self.delete:
os.remove(self.abs_name)


def assert_pdf_equal(test, pdf_or_tmpl, rel_expected_pdf_filepath, delete=True):
"""
Args:
Expand All @@ -33,16 +90,16 @@ def assert_pdf_equal(test, pdf_or_tmpl, rel_expected_pdf_filepath, delete=True):
pdf = pdf_or_tmpl
set_doc_date_0(pdf) # Ensure PDFs CreationDate is always the same
Lucas-C marked this conversation as resolved.
Show resolved Hide resolved
expected_pdf_filepath = relative_path_to(rel_expected_pdf_filepath, depth=2)
with NamedTemporaryFile(
with TempFileCtx(
prefix="pyfpdf-test-", delete=delete, suffix="-actual.pdf"
) as actual_pdf_file:
pdf.output(actual_pdf_file.name, "F")
if not delete:
print("Temporary file will not be deleted:", actual_pdf_file.name)
if QPDF_AVAILABLE: # Favor qpdf-based comparison, as it helps a lot debugging:
with NamedTemporaryFile(
with TempFileCtx(
prefix="pyfpdf-test-", delete=delete, suffix="-actual-qpdf.pdf"
) as actual_qpdf_file, NamedTemporaryFile(
) as actual_qpdf_file, TempFileCtx(
prefix="pyfpdf-test-", delete=delete, suffix="-expected-qpdf.pdf"
) as expected_qpdf_file:
_qpdf(actual_pdf_file.name, actual_qpdf_file.name)
Expand All @@ -54,7 +111,7 @@ def assert_pdf_equal(test, pdf_or_tmpl, rel_expected_pdf_filepath, delete=True):
expected_qpdf_file.name,
)
test.assertSequenceEqual(
actual_qpdf_file.readlines(), expected_qpdf_file.readlines()
actual_qpdf_file.rwf(), expected_qpdf_file.rwf()
)
else: # Fallback to hash comparison
actual_hash = calculate_hash_of_file(actual_pdf_file.name)
Expand Down