diff --git a/.dockerignore b/.dockerignore index 69022fec..5301570a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -277,5 +277,5 @@ dmypy.json # End of https://www.toptal.com/developers/gitignore/api/python,intellij+all,macos,linux -test-data/*.pdf +e2e .github diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 49beb8a8..f5ca3032 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -134,15 +134,17 @@ jobs: runs-on: ubuntu-20.04 needs: - build - services: - pdf: - image: mormahr/pdf-service:sha-${{ github.sha }} - ports: - - 8080:8080 steps: - - name: "Generate basic PDF without returning invalid status code" - run: curl -f --data "
Hello World
" http://localhost:8080/generate > test.pdf + - name: Checkout code + uses: actions/checkout@v2 + + - name: Run visual tests + run: | + cd e2e + export TAG="sha-$GITHUB_SHA" + docker-compose up --build test + tag: runs-on: ubuntu-20.04 if: github.ref == 'refs/heads/main' diff --git a/.gitignore b/.gitignore index 32b6baaf..e1d2553d 100644 --- a/.gitignore +++ b/.gitignore @@ -277,5 +277,5 @@ dmypy.json # End of https://www.toptal.com/developers/gitignore/api/python,intellij+all,macos,linux -test-data/*.pdf +e2e/data/**/*.pdf coverage diff --git a/e2e/.dockerignore b/e2e/.dockerignore new file mode 100644 index 00000000..951594b9 --- /dev/null +++ b/e2e/.dockerignore @@ -0,0 +1,5 @@ +.gitignore +docker-compose.yml +Dockerfile +diffs +data/**/generated.* diff --git a/e2e/.gitignore b/e2e/.gitignore new file mode 100644 index 00000000..b004263e --- /dev/null +++ b/e2e/.gitignore @@ -0,0 +1,2 @@ +diffs +data/**/generated.* diff --git a/e2e/Dockerfile b/e2e/Dockerfile new file mode 100644 index 00000000..dfc141de --- /dev/null +++ b/e2e/Dockerfile @@ -0,0 +1,14 @@ +FROM alpine:3.14 + +WORKDIR /root +ENV PDF_SERVICE_URL=http://pdf:8080 +VOLUME /root/diffs + +RUN apk add --no-cache \ + bash \ + curl \ + imagemagick + +ADD . . + +CMD [ "/bin/bash", "./scripts/run.sh" ] diff --git a/test-data/basic.html b/e2e/data/basic/index.html similarity index 100% rename from test-data/basic.html rename to e2e/data/basic/index.html diff --git a/e2e/data/basic/reference.png b/e2e/data/basic/reference.png new file mode 100644 index 00000000..19f814ce Binary files /dev/null and b/e2e/data/basic/reference.png differ diff --git a/e2e/data/basic/run.sh b/e2e/data/basic/run.sh new file mode 100755 index 00000000..6bb31d0c --- /dev/null +++ b/e2e/data/basic/run.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -e + +if [ "$PDF_SERVICE_URL" = "" ]; then + echo "\$PDF_SERVICE_URL has to be set." +fi + +cd "$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )" + +curl \ + --fail \ + --silent \ + -F index.html=@index.html \ + "$PDF_SERVICE_URL/generate" \ + > generated.pdf + +../../scripts/create_reference_or_diff.sh diff --git a/test-data/fonts.html b/e2e/data/fonts/index.html similarity index 100% rename from test-data/fonts.html rename to e2e/data/fonts/index.html diff --git a/e2e/data/fonts/reference.png b/e2e/data/fonts/reference.png new file mode 100644 index 00000000..f8b352a3 Binary files /dev/null and b/e2e/data/fonts/reference.png differ diff --git a/e2e/data/fonts/run.sh b/e2e/data/fonts/run.sh new file mode 100755 index 00000000..6bb31d0c --- /dev/null +++ b/e2e/data/fonts/run.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -e + +if [ "$PDF_SERVICE_URL" = "" ]; then + echo "\$PDF_SERVICE_URL has to be set." +fi + +cd "$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )" + +curl \ + --fail \ + --silent \ + -F index.html=@index.html \ + "$PDF_SERVICE_URL/generate" \ + > generated.pdf + +../../scripts/create_reference_or_diff.sh diff --git a/test-data/multipart-with-image/test.png b/e2e/data/multipart-with-image/assets/test.png similarity index 100% rename from test-data/multipart-with-image/test.png rename to e2e/data/multipart-with-image/assets/test.png diff --git a/test-data/multipart-with-image.html b/e2e/data/multipart-with-image/index.html similarity index 100% rename from test-data/multipart-with-image.html rename to e2e/data/multipart-with-image/index.html diff --git a/e2e/data/multipart-with-image/reference.png b/e2e/data/multipart-with-image/reference.png new file mode 100644 index 00000000..f289f1db Binary files /dev/null and b/e2e/data/multipart-with-image/reference.png differ diff --git a/e2e/data/multipart-with-image/run.sh b/e2e/data/multipart-with-image/run.sh new file mode 100755 index 00000000..50bbf379 --- /dev/null +++ b/e2e/data/multipart-with-image/run.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +set -e + +if [ "$PDF_SERVICE_URL" = "" ]; then + echo "\$PDF_SERVICE_URL has to be set." +fi + +cd "$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )" + +curl \ + --fail \ + --silent \ + -F index.html=@index.html \ + -F test.png=@assets/test.png \ + "$PDF_SERVICE_URL/generate" \ + > generated.pdf + +../../scripts/create_reference_or_diff.sh diff --git a/e2e/docker-compose.yml b/e2e/docker-compose.yml new file mode 100644 index 00000000..0bca7dc5 --- /dev/null +++ b/e2e/docker-compose.yml @@ -0,0 +1,8 @@ +version: "3.8" +services: + pdf: + image: "mormahr/pdf-service:${TAG}" + test: + build: . + links: + - "pdf:pdf" diff --git a/e2e/regenerate b/e2e/regenerate new file mode 120000 index 00000000..e9a240fb --- /dev/null +++ b/e2e/regenerate @@ -0,0 +1 @@ +scripts/regenerate.sh \ No newline at end of file diff --git a/e2e/run b/e2e/run new file mode 120000 index 00000000..8c48acdb --- /dev/null +++ b/e2e/run @@ -0,0 +1 @@ +scripts/run.sh \ No newline at end of file diff --git a/e2e/scripts/clean.sh b/e2e/scripts/clean.sh new file mode 100755 index 00000000..49a05570 --- /dev/null +++ b/e2e/scripts/clean.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +set -e + +cd "$( cd "$(dirname "${BASH_SOURCE[0]}")" || exit; pwd -P )" || exit + +rm -f ../data/**/diff.png +rm -f ../data/**/generated.* diff --git a/e2e/scripts/convert.sh b/e2e/scripts/convert.sh new file mode 100755 index 00000000..1ee272f5 --- /dev/null +++ b/e2e/scripts/convert.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +set -e + +convert -density 200 -depth 8 -quality 100 generated.pdf -background white -flatten -strip PNG24:$1 diff --git a/e2e/scripts/create_reference_or_diff.sh b/e2e/scripts/create_reference_or_diff.sh new file mode 100755 index 00000000..8a6b10f8 --- /dev/null +++ b/e2e/scripts/create_reference_or_diff.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +set -e + +if [ ! -f reference.png ]; then + # Reference file doesn't exist yet -> create + echo "Generating for $(basename "$PWD")" + ../../scripts/convert.sh reference.png +else + ../../scripts/convert.sh generated.png + + # Remove old diffs + mkdir -p ../../diffs/ + rm -f "../../diffs/$(basename "$PWD").*" + + # Create diff image + # Ignore comparison errors here because we do a byte comparison later + compare reference.png generated.png -compose Src "../../diffs/$(basename "$PWD").png" || true + + # Generate animated GIF to illustrate differences + convert -delay 50 reference.png generated.png -loop 0 "../../diffs/$(basename "$PWD").gif" + + # Byte comparison of reference and current image + if ! cmp reference.png generated.png > /dev/null 2>&1 + then + echo "Reference and generated image differ for $(basename "$PWD")" + exit 1 + fi +fi diff --git a/e2e/scripts/regenerate.sh b/e2e/scripts/regenerate.sh new file mode 100755 index 00000000..52f52594 --- /dev/null +++ b/e2e/scripts/regenerate.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +set -e + +cd "$( cd "$(dirname "${BASH_SOURCE[0]}")" || exit; pwd -P )" || exit + +./clean.sh +rm -f ../data/**/reference.png +./run.sh diff --git a/e2e/scripts/run.sh b/e2e/scripts/run.sh new file mode 100755 index 00000000..ce2980c2 --- /dev/null +++ b/e2e/scripts/run.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +if [ "$PDF_SERVICE_URL" = "" ]; then + echo "\$PDF_SERVICE_URL has to be set." +fi + +# Wait for pdf service to become available. (1s retry, 30s timeout) +timeout 30 bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' $PDF_SERVICE_URL/health)" != "200" ]]; do sleep 1; done' || false + +cd "$( cd "$(dirname "${BASH_SOURCE[0]}")" || exit; pwd -P )" || exit + +for f in ../data/* +do + # Guard against empty directory + [ -e "$f" ] || continue + echo "Running $(basename "$f")" + + # Propagate errors because -e isn't set + "$f/run.sh" || exit 1 +done diff --git a/test-data/basic_0001-1.png b/test-data/basic_0001-1.png deleted file mode 100644 index 7150e1bf..00000000 Binary files a/test-data/basic_0001-1.png and /dev/null differ diff --git a/test-data/fonts_0001-1.png b/test-data/fonts_0001-1.png deleted file mode 100644 index f95126d3..00000000 Binary files a/test-data/fonts_0001-1.png and /dev/null differ diff --git a/test-data/multipart-with-image_0001-1.png b/test-data/multipart-with-image_0001-1.png deleted file mode 100644 index fb2a3aca..00000000 Binary files a/test-data/multipart-with-image_0001-1.png and /dev/null differ diff --git a/tests/test_visual.py b/tests/test_visual.py deleted file mode 100644 index 2d30f7b8..00000000 --- a/tests/test_visual.py +++ /dev/null @@ -1,54 +0,0 @@ -import pytest - -from pdf_service import pdf_service -from pdf2image import convert_from_bytes -from pathlib import Path -import tempfile -from diffimg import diff - - -@pytest.fixture -def client(): - with pdf_service.test_client() as client: - yield client - - -def resolve_all_tests(): - base = Path(__file__).parent.joinpath("../test-data") - return [file.stem.replace(".html", "") for file in base.glob("*.html")] - - -@pytest.mark.parametrize("name", resolve_all_tests()) -def test_matches_visually(client, name): - base = Path(__file__).parent.joinpath("../test-data") - - if base.joinpath(name).exists(): - data = { - 'index.html': (base.joinpath(name + ".html").open('rb'), 'index.html', 'text/html'), - } - - for file in base.joinpath(name).iterdir(): - data[file.name] = (file.open('rb'), file.name, 'image/png') - - rv = client.post('/generate', data=data, content_type="multipart/form-data") - else: - html = base.joinpath(name + ".html").read_text() - rv = client.post('/generate', data=html, content_type="text/html") - - assert 200 == rv.status_code - - temp = tempfile.mkdtemp() - paths = convert_from_bytes(rv.data, - output_file=name + "_", - output_folder=temp, - fmt="png", - paths_only=True) - - original_files = base.glob(name + "_*.png") - assert sum(1 for _ in original_files) == len(paths), "Number of generated pages differs from " \ - "expected " - - for expected, actual in zip(base.glob(name + "_*.png"), paths): - percentage = diff(expected, actual, diff_img_file=tempfile.mktemp(".png")) * 100 - assert percentage == 0.0, "Rasterized page differs from expected result by more than the " \ - "allowed threshold "