From db1339f4885c1cf248a5a8753dcbaff316723107 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9rome=20Perrin?= <jerome@nexedi.com> Date: Thu, 19 Mar 2020 04:20:57 +0100 Subject: [PATCH] software/cloudooo/test: software release test Check what fonts are used when converting to PDF, using HTML as input. --- software/cloudooo/test/README.md | 1 + software/cloudooo/test/setup.py | 52 +++++++ software/cloudooo/test/test.py | 240 +++++++++++++++++++++++++++++++ 3 files changed, 293 insertions(+) create mode 100644 software/cloudooo/test/README.md create mode 100644 software/cloudooo/test/setup.py create mode 100644 software/cloudooo/test/test.py diff --git a/software/cloudooo/test/README.md b/software/cloudooo/test/README.md new file mode 100644 index 0000000000..97880a8920 --- /dev/null +++ b/software/cloudooo/test/README.md @@ -0,0 +1 @@ +Tests for Cloudooo software release diff --git a/software/cloudooo/test/setup.py b/software/cloudooo/test/setup.py new file mode 100644 index 0000000000..325f4fa3d7 --- /dev/null +++ b/software/cloudooo/test/setup.py @@ -0,0 +1,52 @@ +############################################################################## +# +# Copyright (c) 2018 Nexedi SA and Contributors. All Rights Reserved. +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsibility of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# guarantees and support are strongly adviced to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 3 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## +from setuptools import setup, find_packages + +version = '0.0.1.dev0' +name = 'slapos.test.cloudooo' +with open("README.md") as f: + long_description = f.read() + +setup(name=name, + version=version, + description="Test for SlapOS' cloudooo", + long_description=long_description, + long_description_content_type='text/markdown', + maintainer="Nexedi", + maintainer_email="info@nexedi.com", + url="https://lab.nexedi.com/nexedi/slapos", + packages=find_packages(), + install_requires=[ + 'slapos.core', + 'slapos.cookbook', + 'slapos.libnetworkcache', + 'six', + 'PyPDF2', + ], + zip_safe=True, + test_suite='test', + ) diff --git a/software/cloudooo/test/test.py b/software/cloudooo/test/test.py new file mode 100644 index 0000000000..e9b5ddcb95 --- /dev/null +++ b/software/cloudooo/test/test.py @@ -0,0 +1,240 @@ +############################################################################## +# coding: utf-8 +# +# Copyright (c) 2020 Nexedi SA and Contributors. All Rights Reserved. +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsibility of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# guarantees and support are strongly adviced to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 3 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +import os +import json +import six.moves.xmlrpc_client as xmlrpclib +import ssl +import base64 +import io + +import PyPDF2 + +from slapos.testing.testcase import makeModuleSetUpAndTestCaseClass + +setUpModule, CloudOooTestCase = makeModuleSetUpAndTestCaseClass( + os.path.abspath( + os.path.join(os.path.dirname(__file__), '..', 'software.cfg'))) + +# Cloudooo needs a lot of time before being available. +CloudOooTestCase.instance_max_retry = 30 + + +def normalizeFontName(font_name): + if '+' in font_name: + return font_name.split('+')[1] + if font_name.startswith('/'): + return font_name[1:] + + +def getReferencedFonts(pdf_file_reader): + """Return fonts referenced in this pdf + """ + fonts = set() + + def collectFonts(obj): + """Recursively visit PDF objects and collect referenced fonts in `fonts` + """ + if hasattr(obj, 'keys'): + if '/BaseFont' in obj: + fonts.add(obj['/BaseFont']) + for k in obj.keys(): + collectFonts(obj[k]) + + for page in pdf_file_reader.pages: + collectFonts(page.getObject()['/Resources']) + + return {normalizeFontName(font) for font in fonts} + + +class HTMLtoPDFConversionFontTestMixin: + """Mix-In class to test how fonts are selected during + HTML to PDF conversions. + + This needs to be mixed with a test case defining: + + * pdf_producer : the name of /Producer in PDF metadata + * expected_font_mapping : a mapping of resulting font name in pdf, + keyed by font-family in the input html + * _convert_html_to_pdf: a method to to convert html to pdf + """ + def _convert_html_to_pdf(self, src_html): + # type: (str) -> bytes + """Convert the HTML source to pdf bytes. + """ + + def setUp(self): + self.url = json.loads( + self.computer_partition.getConnectionParameterDict()["_"])['cloudooo'] + # XXX ignore certificate errors + ssl_context = ssl.create_default_context() + ssl_context.check_hostname = False + ssl_context.verify_mode = ssl.CERT_NONE + self.server = xmlrpclib.ServerProxy( + self.url, + context=ssl_context, + allow_none=True, + ) + + def test(self): + actual_font_mapping_mapping = {} + for font, expected_substitution in sorted( + self.expected_font_mapping.items()): + src_html = ''' + <style> + p {{ font-family: "{font}"; font-size: 20pt; }} + </style> + <p>the quick brown fox jumps over the lazy dog.</p> + <p>THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG.</p> + '''.format(**locals()) + + pdf_data = self._convert_html_to_pdf(src_html) + pdf_reader = PyPDF2.PdfFileReader(io.BytesIO((pdf_data))) + self.assertEqual( + self.pdf_producer, + pdf_reader.getDocumentInfo()['/Producer']) + fonts_in_pdf = getReferencedFonts(pdf_reader) + + if len(fonts_in_pdf) == 1: + actual_font_mapping_mapping[font] = fonts_in_pdf.pop() + else: + actual_font_mapping_mapping[font] = fonts_in_pdf + + self.maxDiff = None + self.assertEqual(self.expected_font_mapping, actual_font_mapping_mapping) + + +class TestWkhtmlToPDF(HTMLtoPDFConversionFontTestMixin, CloudOooTestCase): + __partition_reference__ = 'wk' + pdf_producer = 'Qt 4.8.7' + expected_font_mapping = { + 'Arial Black': 'Roboto-Medium', + 'Arial': 'Roboto-Medium', + 'Avant Garde': 'Roboto-Medium', + 'Bookman': 'Roboto-Medium', + 'Carlito': 'Roboto-Medium', + 'Comic Sans MS': 'Roboto-Medium', + 'Courier New': 'Roboto-Medium', + 'DejaVu Sans Condensed': 'Roboto-Medium', + 'DejaVu Sans ExtraLight': 'Roboto-Medium', + 'DejaVu Sans Mono': 'Roboto-Medium', + 'DejaVu Sans': 'Roboto-Medium', + 'DejaVu Serif Condensed': 'Roboto-Medium', + 'DejaVu Serif': 'Roboto-Medium', + 'Garamond': 'Roboto-Medium', + 'Gentium Basic': 'Roboto-Medium', + 'Gentium Book Basic': 'Roboto-Medium', + 'Georgia': 'Roboto-Medium', + 'Helvetica': 'Roboto-Medium', + 'Impact': 'Roboto-Medium', + 'IPAex Gothic': 'Roboto-Medium', + 'IPAex Mincho': 'Roboto-Medium', + 'Liberation Mono': 'LiberationMono', + 'Liberation Sans Narrow': 'Roboto-Medium', + 'Liberation Sans': 'LiberationSans', + 'Liberation Serif': 'LiberationSerif', + 'Linux LibertineG': 'Roboto-Medium', + 'OpenSymbol': 'Roboto-Medium', + 'Palatino': 'Roboto-Medium', + 'Roboto Black': 'Roboto-Medium', + 'Roboto Condensed Light': 'Roboto-Medium', + 'Roboto Condensed Regular': 'Roboto-Medium', + 'Roboto Light': 'Roboto-Medium', + 'Roboto Medium': 'Roboto-Medium', + 'Roboto Thin': 'Roboto-Medium', + 'Times New Roman': 'Roboto-Medium', + 'Trebuchet MS': 'Roboto-Medium', + 'Verdana': 'Roboto-Medium', + 'ZZZdefault fonts when no match': 'Roboto-Medium', + } + + def _convert_html_to_pdf(self, src_html): + return base64.decodestring( + self.server.convertFile( + base64.encodestring(src_html.encode()).decode(), + 'html', + 'pdf', + False, + False, + { + 'encoding': 'utf-8' + }, + ).encode()) + + +class TestLibreoffice(HTMLtoPDFConversionFontTestMixin, CloudOooTestCase): + __partition_reference__ = 'lo' + pdf_producer = 'LibreOffice 5.2' + expected_font_mapping = { + 'Arial Black': 'LinuxLibertineG', + 'Arial': 'LinuxLibertineG', + 'Avant Garde': 'LinuxLibertineG', + 'Bookman': 'LinuxLibertineG', + 'Carlito': 'Carlito', + 'Comic Sans MS': 'LinuxLibertineG', + 'Courier New': 'LinuxLibertineG', + 'DejaVu Sans Condensed': 'DejaVuSansCondensed', + 'DejaVu Sans ExtraLight': 'LinuxLibertineG', + 'DejaVu Sans Mono': 'DejaVuSansMono', + 'DejaVu Sans': 'DejaVuSans', + 'DejaVu Serif Condensed': 'DejaVuSerifCondensed', + 'DejaVu Serif': 'DejaVuSerif', + 'Garamond': 'LinuxLibertineG', + 'Gentium Basic': 'GentiumBasic', + 'Gentium Book Basic': 'GentiumBookBasic', + 'Georgia': 'LinuxLibertineG', + 'Helvetica': 'LinuxLibertineG', + 'Impact': 'LinuxLibertineG', + 'IPAex Gothic': 'IPAexGothic', + 'IPAex Mincho': 'IPAexMincho', + 'Liberation Mono': 'LiberationMono', + 'Liberation Sans Narrow': 'LiberationSansNarrow', + 'Liberation Sans': 'LiberationSans', + 'Liberation Serif': 'LiberationSerif', + 'Linux LibertineG': 'LinuxLibertineG', + 'OpenSymbol': 'OpenSymbol', + 'Palatino': 'LinuxLibertineG', + 'Roboto Black': 'Roboto-Black', + 'Roboto Condensed Light': 'RobotoCondensed-Light', + 'Roboto Condensed Regular': 'LinuxLibertineG', + 'Roboto Light': 'Roboto-Light', + 'Roboto Medium': 'Roboto-Medium', + 'Roboto Thin': 'Roboto-Thin', + 'Times New Roman': 'LinuxLibertineG', + 'Trebuchet MS': 'LinuxLibertineG', + 'Verdana': 'LinuxLibertineG', + 'ZZZdefault fonts when no match': 'LinuxLibertineG', + } + + def _convert_html_to_pdf(self, src_html): + return base64.decodestring( + self.server.convertFile( + base64.encodestring(src_html.encode()).decode(), + 'html', + 'pdf', + ).encode()) -- 2.30.9