From cc2a58bddcd8ac2436a9eb598dd879ecc8939e32 Mon Sep 17 00:00:00 2001 From: Nemo Date: Sun, 4 Jul 2021 12:57:18 +0530 Subject: [PATCH] Add Tests (#13) Basic functional tests that cover 90% of the usecases. Doesn't cover zoomlevel, remote fetch yet. --- .github/workflows/tests.yml | 29 ++++++++++ setup.cfg | 6 +- tests/book-clean.md | 1 + tests/book-flatten.md | 1 - tests/book-headings.md | 10 ++++ tests/{min.md => book-min.md} | 0 tests/book-page-select.md | 5 +- tests/book-rotate.md | 3 - tests/{title.md => book-title.md} | 0 tests/test_integration.py | 96 +++++++++++++++++++++++++++++++ tests/test_skeleton.py | 25 -------- 11 files changed, 140 insertions(+), 36 deletions(-) create mode 100644 .github/workflows/tests.yml create mode 100644 tests/book-headings.md rename tests/{min.md => book-min.md} (100%) rename tests/{title.md => book-title.md} (100%) create mode 100644 tests/test_integration.py delete mode 100644 tests/test_skeleton.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..5f38f0b --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,29 @@ +name: Run Tests +on: push +jobs: + all: + runs-on: ubuntu-latest + strategy: + matrix: + python: ["3.6", "3.7", "3.8", "3.9"] + env: + PYTHON_VERSION: ${{matrix.python}} + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{matrix.python}} + uses: actions/setup-python@v2 + with: + python-version: ${{matrix.python}} + - name: Install deps + run: | + python -m pip install --upgrade pip + pip install -e .[testing] + - name: Run pytest + run: | + pytest --cache-clear --cov=./ --cov-report=xml --cov-report=html + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: ./coverage.xml + env_vars: RUNNER_OS,PYTHON_VERSION,CI,GITHUB_SHA,RUNNER_OS,GITHUB_RUN_ID diff --git a/setup.cfg b/setup.cfg index 3576488..fde2ac6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -82,9 +82,9 @@ console_scripts = # in order to write a coverage file that can be read by Jenkins. # CAUTION: --cov flags may prohibit setting breakpoints while debugging. # Comment those flags to avoid this py.test issue. -addopts = - --cov pystitcher --cov-report term-missing - --verbose +addopts = --verbose + # --cov pystitcher --cov-report term-missing + norecursedirs = dist build diff --git a/tests/book-clean.md b/tests/book-clean.md index 95ac78b..b1435ab 100644 --- a/tests/book-clean.md +++ b/tests/book-clean.md @@ -1,5 +1,6 @@ existing_bookmarks: remove author: Wiki, the Cat +title: Super Jelly Book subject: A book about adventures of Wiki, the cat. keywords: wiki,potato,jelly # Super Potato Book diff --git a/tests/book-flatten.md b/tests/book-flatten.md index 239cb4b..c18cc01 100644 --- a/tests/book-flatten.md +++ b/tests/book-flatten.md @@ -1,5 +1,4 @@ existing_bookmarks: flatten -title: Super Jelly Book # Super Potato Book diff --git a/tests/book-headings.md b/tests/book-headings.md new file mode 100644 index 0000000..3cc2ba5 --- /dev/null +++ b/tests/book-headings.md @@ -0,0 +1,10 @@ +# Heading 1 +[Part 1](1.pdf) + +## Heading 2 + +[Part 2](1.pdf) + +### Heading 3 + +[Part 3](1.pdf) diff --git a/tests/min.md b/tests/book-min.md similarity index 100% rename from tests/min.md rename to tests/book-min.md diff --git a/tests/book-page-select.md b/tests/book-page-select.md index 83b9136..e5f6537 100644 --- a/tests/book-page-select.md +++ b/tests/book-page-select.md @@ -1,7 +1,4 @@ -existing_bookmarks: remove -author: Wiki, the Cat -subject: A book about adventures of Wiki, the cat. -keywords: wiki,potato,jelly +existing_bookmarks: keep # Super Potato Book # Volume 1 diff --git a/tests/book-rotate.md b/tests/book-rotate.md index 0b1ef0b..a70509a 100644 --- a/tests/book-rotate.md +++ b/tests/book-rotate.md @@ -1,7 +1,4 @@ existing_bookmarks: remove -author: Wiki, the Cat -subject: A book about adventures of Wiki, the cat. -keywords: wiki,potato,jelly # Super Potato Book # Volume 1 diff --git a/tests/title.md b/tests/book-title.md similarity index 100% rename from tests/title.md rename to tests/book-title.md diff --git a/tests/test_integration.py b/tests/test_integration.py new file mode 100644 index 0000000..a41565e --- /dev/null +++ b/tests/test_integration.py @@ -0,0 +1,96 @@ +import os +import io + +import PyPDF3 +from pystitcher.stitcher import Stitcher +from pystitcher import __version__ + +import pytest +from contextlib import redirect_stdout + +ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) + "/../" + +""" +Fixtures for the integration tests. Each test is a tuple consisting of 4 things: +- input name (used as book-{name}.md) +- total expected page count +- A dictionary of expected metadata. Leave empty if nothing is set +- A flattened list of expected bookmarks, with each bookmark as a tuple containing: + - Title + - Destination Page Number + - Bookmark Level (default = 0) +Each of the above 4 is passed to test_book as an argument +""" +TEST_DATA = [ + ("clean",6, {'Author': 'Wiki, the Cat', 'Title': 'Super Jelly Book', 'Subject': 'A book about adventures of Wiki, the cat.', 'Keywords': 'wiki,potato,jelly'}, [('Super Potato Book', 0, 0), ('Volume 1', 0, 0), ('Part 1', 0, 1), ('Volume 2', 3, 0), ('Part 2', 3, 1)]), + ("keep",6, {'Title': 'Super Potato Book'}, [('Super Potato Book', 0, 0), ('Volume 1', 0, 0), ('Part 1', 0, 1), ('Chapter 1', 0, 2), ('Chapter 2', 1, 2), ('Scene 1', 1, 3), ('Scene 2', 2, 3), ('Volume 2', 3, 0), ('Part 3', 3, 1), ('Chapter 3', 3, 2), ('Chapter 4', 4, 2), ('Scene 3', 4, 3), ('Scene 4', 5, 3)]), + ("flatten", 6, {}, [('Super Potato Book', 0, 0), ('Volume 1', 0, 0), ('Part 1', 0, 1), ('Chapter 1', 0, 2), ('Chapter 2', 1, 2), ('Scene 1', 1, 2), ('Scene 2', 2, 2), ('Volume 2', 3, 0), ('Part 3', 3, 1), ('Chapter 3', 3, 2), ('Chapter 4', 4, 2), ('Scene 3', 4, 2), ('Scene 4', 5, 2)]), + ("rotate", 9, {}, [('Super Potato Book', 0, 0), ('Volume 1', 0, 0), ('Part 1', 0, 1), ('Volume 2', 3, 0), ('Part 2', 3, 1), ('Volume 3', 6, 0), ('Part 3', 6, 1)]), + ("min",3, {}, [('Part 1', 0, 0), ('Chapter 1', 0, 1), ('Chapter 2', 1, 1), ('Scene 1', 1, 2), ('Scene 2', 2, 2)]), + ("page-select", 9, {}, [('Super Potato Book', 0, 0), ('Volume 1', 0, 0), ('Part 1', 0, 1), ('Chapter 1', 0, 2), ('Chapter 2', 1, 2), ('Scene 1', 1, 3), ('Volume 2', 2, 0), ('Part 2', 2, 1), ('Scene 2', 2, 2), ('Chapter 3', 2, 2), ('Chapter 4', 3, 2), ('Scene 3', 3, 3), ('Volume 3', 4, 0), ('Part 3', 4, 1), ('Scene 4', 4, 2), ('Chapter 1', 4, 2), ('Chapter 2', 5, 2), ('Scene 1', 5, 3), ('Volume 4', 6, 0), ('Part 4', 6, 1), ('Scene 2', 6, 2), ('Chapter 3', 6, 2), ('Chapter 4', 7, 2), ('Scene 3', 7, 3), ('Scene 4', 8, 3)]), + ("headings", 9, {'Title': 'Heading 1'}, [('Heading 1', 0, 0), ('Part 1', 0, 1), ('Heading 2', 3, 1), ('Part 2', 3, 2), ('Heading 3', 6, 2), ('Part 3', 6, 3)]) +] + +def pdf_name(name): + return "tests/%s.pdf" % name + +def render(name, cleanup=True): + input_file = open("tests/book-%s.md" % name, 'r') + output_file = "%s.pdf" % name + stitcher = Stitcher(input_file) + stitcher.generate(output_file, cleanup) + # Switch back to main directory + os.chdir(ROOT_DIR) + return pdf_name(name) + +def flatten_bookmarks(bookmarks, level=0): + """Given a list, possibly nested to any level, return it flattened.""" + output = [] + for destination in bookmarks: + if type(destination) == type([]): + output.extend(flatten_bookmarks(destination, level+1)) + else: + output.append((destination, level)) + return output + +def get_all_bookmarks(pdf): + """ Returns a list of all bookmarks with title, page number, and level in a PDF file""" + bookmarks = flatten_bookmarks(pdf.getOutlines()) + return [(d[0]['/Title'], pdf.getDestinationPageNumber(d[0]), d[1]) for d in bookmarks] + +@pytest.mark.parametrize("name,pages,metadata,bookmarks", TEST_DATA) +def test_book(name, pages, metadata, bookmarks): + output_file = render(name) + pdf = PyPDF3.PdfFileReader(output_file) + assert pages == pdf.getNumPages() + assert bookmarks == get_all_bookmarks(pdf) + info = pdf.getDocumentInfo() + identity = "pystitcher/%s" % __version__ + assert identity == info['/Producer'] + assert identity == info['/Creator'] + for key in metadata: + assert info["/%s" % key] == metadata[key] + +def test_rotation(): + """ Validates the book-rotate.pdf with pages rotated.""" + output_file = render("rotate") + pdf = PyPDF3.PdfFileReader(output_file) + # Note that inputs to getPage are 0-indexed + assert 90 == pdf.getPage(3)['/Rotate'] + assert 90 == pdf.getPage(4)['/Rotate'] + assert 90 == pdf.getPage(5)['/Rotate'] + assert 180 == pdf.getPage(6)['/Rotate'] + assert 180 == pdf.getPage(7)['/Rotate'] + assert 180 == pdf.getPage(8)['/Rotate'] + +def test_cleanup_disabled(): + f = io.StringIO() + with redirect_stdout(f): + output_file = render("min", False) + temp_filename = f.getvalue()[29:-1] + assert os.path.exists(temp_filename) + pdf = PyPDF3.PdfFileReader(temp_filename) + assert 3 == pdf.getNumPages() + assert [] == pdf.getOutlines() + # Clean it up manually to avoid cluttering + os.remove(temp_filename) diff --git a/tests/test_skeleton.py b/tests/test_skeleton.py deleted file mode 100644 index 3470f08..0000000 --- a/tests/test_skeleton.py +++ /dev/null @@ -1,25 +0,0 @@ -import pytest - -from pystitcher.skeleton import fib, main - -__author__ = "Nemo" -__copyright__ = "Nemo" -__license__ = "MIT" - - -def test_fib(): - """API Tests""" - assert fib(1) == 1 - assert fib(2) == 1 - assert fib(7) == 13 - with pytest.raises(AssertionError): - fib(-10) - - -def test_main(capsys): - """CLI Tests""" - # capsys is a pytest fixture that allows asserts agains stdout/stderr - # https://docs.pytest.org/en/stable/capture.html - main(["7"]) - captured = capsys.readouterr() - assert "The 7-th Fibonacci number is 13" in captured.out