From edbb88f045ad5d7bba6e3aee22dcce1d56049f08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Mond=C3=A9jar=20Rubio?= Date: Sun, 27 Feb 2022 19:27:19 +0100 Subject: [PATCH] Add tests --- mkdocs_mdpo_plugin/plugin.py | 19 ++- tests/conftest.py | 157 ++++++++++-------- tests/test_min_translated_messages.py | 122 ++++++++++++++ .../test_api_documentation_building.py | 2 +- 4 files changed, 226 insertions(+), 74 deletions(-) diff --git a/mkdocs_mdpo_plugin/plugin.py b/mkdocs_mdpo_plugin/plugin.py index 0bf9d5c..e8d7425 100644 --- a/mkdocs_mdpo_plugin/plugin.py +++ b/mkdocs_mdpo_plugin/plugin.py @@ -1,6 +1,7 @@ """mkdocs-mdpo-plugin module""" import functools +import logging import math import os import sys @@ -33,6 +34,10 @@ ) +# use Mkdocs build logger +logger = logging.getLogger('mkdocs.commands.build') + + class MdpoPlugin(mkdocs.plugins.BasePlugin): config_scheme = CONFIG_SCHEME @@ -418,8 +423,8 @@ def on_post_page(self, output, page, config): stats['percent_translated'] = percent_translated if percent_translated < min_translated: if language in self.config['languages']: - sys.stdout.write( - 'INFO - [mdpo] ' + logger.info( + '[mdpo] ' f'Excluding language "{language}". Translated' f' {readable_float(percent_translated)}%' f' ({stats["translated"]} of' @@ -432,13 +437,13 @@ def on_post_page(self, output, page, config): else: if stats['translated'] < min_translated: if language in self.config['languages']: - sys.stdout.write( - 'INFO - [mdpo] ' + logger.info( + '[mdpo] ' f'Excluding language "{language}".' f' Translated {stats["translated"]} messages' f' of {stats["total"]} but required' f' {min_translated} translated' - 'messages at least.\n', + ' messages at least.\n', ) self.config['languages'].remove(language) return @@ -572,8 +577,8 @@ def on_serve(self, *args, **kwargs): # pragma: no cover directory. """ if '..' not in self.config['locale_dir']: - sys.stderr.write( - 'ERROR [mdpo] - ' + logger.error( + '[mdpo] - ' "You need to set 'locale_dir' configuration setting" ' pointing to a directory placed outside' " the documentation directory ('docs_dir') in order to" diff --git a/tests/conftest.py b/tests/conftest.py index f562215..b7be97b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,8 +1,9 @@ """Configuration for mkdocs_mdpo_plugin tests.""" +import logging import os import sys -from tempfile import TemporaryDirectory +from tempfile import NamedTemporaryFile, TemporaryDirectory import polib import pytest @@ -28,9 +29,21 @@ def _mkdocs_build( callback_after_first_build=None, insert_plugin_config_at_position=-1, interrupt_after_first_build=False, + allow_missing_translations=False, ): with TemporaryDirectory() as site_dir, TemporaryDirectory() as docs_dir, \ - TemporaryDirectory() as config_dir: + TemporaryDirectory() as config_dir, \ + NamedTemporaryFile('w+', suffix='.log') as mkdocs_logger_f, \ + NamedTemporaryFile('w+', suffix='.log') as plugin_logger_f: + + # configure mkdocs logger to capture messages + mkdocs_logger = logging.getLogger('mkdocs') + mkdocs_logger.setLevel(logging.DEBUG) + mkdocs_logger.addHandler(logging.FileHandler(mkdocs_logger_f.name)) + + plugin_logger = logging.getLogger('mkdocs.commands.build') + plugin_logger.setLevel(logging.DEBUG) + plugin_logger.addHandler(logging.FileHandler(plugin_logger_f.name)) # build input files for input_file_name, content in input_files_contents.items(): @@ -85,80 +98,92 @@ def _mkdocs_build( if interrupt_after_first_build: os.remove(config_filename) - return - - # translate PO files - for po_filename, translation_messages in translations.items(): - po_filename = os.path.join(docs_dir, os.path.normpath(po_filename)) - assert os.path.isfile(po_filename) - po = polib.pofile(po_filename) - - for msgid_or_msgctxt, msgstr in translation_messages.items(): - if isinstance(msgstr, dict): - # case when msgctxt is passed as key - # and msgid-msgstr as value in a dict - msgid = list(msgstr.keys())[0] - msgstr = msgstr[msgid] - msgctxt = msgid_or_msgctxt - else: - msgid = msgid_or_msgctxt - msgctxt = None - - _msgid_in_pofile = False - for entry in po: - if entry.msgid == msgid: - _msgid_in_pofile = True - break + else: - assert _msgid_in_pofile, ( - f"'{msgid}' not found in pofile '{po_filename}'" + # translate PO files + for po_filename, translation_messages in translations.items(): + po_filename = os.path.join( + docs_dir, + os.path.normpath(po_filename), ) + assert os.path.isfile(po_filename) + po = polib.pofile(po_filename) + + for msgid_or_msgctxt, msgstr in translation_messages.items(): + if isinstance(msgstr, dict): + # case when msgctxt is passed as key + # and msgid-msgstr as value in a dict + msgid = list(msgstr.keys())[0] + msgstr = msgstr[msgid] + msgctxt = msgid_or_msgctxt + else: + msgid = msgid_or_msgctxt + msgctxt = None + + _msgid_in_pofile = False + for entry in po: + if entry.msgid == msgid: + _msgid_in_pofile = True + break + + assert _msgid_in_pofile, ( + f"'{msgid}' not found in pofile '{po_filename}'" + ) + + for entry in po: + if entry.msgid == msgid: + entry.msgstr = msgstr + if msgctxt: + entry.msgctxt = msgctxt + break for entry in po: - if entry.msgid == msgid: - entry.msgstr = msgstr - if msgctxt: - entry.msgctxt = msgctxt - break - - for entry in po: - # 'Home' is the title given to the page by the default - # Mkdocs theme - if entry.msgid == 'Home': - continue - assert entry.msgstr, ( - f"Found '{entry.msgid}' not translated in pofile" - ) + # 'Home' is the title given to the page by the default + # Mkdocs theme + if entry.msgid == 'Home': + continue + if not allow_missing_translations: + assert entry.msgstr, ( + f"Found '{entry.msgid}' not translated in pofile" + ) + + po.save(po_filename) + + # second build, dump translations in content (PO files -> Markdown) + try: + build(config.load_config(config_filename)) + except Exception: + os.remove(config_filename) + raise + + # assert that files have been translated + for filename, expected_lines in expected_output_files.items(): + if not expected_lines: + raise ValueError( + f'Expected file "{filename}" defined without output' + ' lines', + ) - po.save(po_filename) + filepath = os.path.join(site_dir, os.path.normpath(filename)) - # second build, dump translations in content (PO files -> Markdown) - try: - build(config.load_config(config_filename)) - except Exception: - os.remove(config_filename) - raise - - # assert that files have been translated - for filename, expected_lines in expected_output_files.items(): - if not expected_lines: - raise ValueError( - f'Expected file "{filename}" defined without output' - ' lines', - ) + with open(filepath) as f: + content = f.read() - filepath = os.path.join(site_dir, os.path.normpath(filename)) + for expected_line in expected_lines: + assert expected_line in content, ( + f'Expected line "{expected_line}" not found in file' + f' "{filename}"' + ) - with open(filepath) as f: - content = f.read() + os.remove(config_filename) - for expected_line in expected_lines: - assert expected_line in content, ( - f'Expected line "{expected_line}" not found in file' - f' "{filename}"' - ) + # read builds logs + with open(mkdocs_logger_f.name) as f: + mkdocs_log = f.read() + with open(plugin_logger_f.name) as f: + plugin_log = f.read() - os.remove(config_filename) + return (mkdocs_log, plugin_log) @pytest.fixture diff --git a/tests/test_min_translated_messages.py b/tests/test_min_translated_messages.py index e69de29..44ca5ec 100644 --- a/tests/test_min_translated_messages.py +++ b/tests/test_min_translated_messages.py @@ -0,0 +1,122 @@ +"""Tests for "min_translated_messages" configuration setting.""" + +import os + +import pytest + + +TESTS = ( + pytest.param( + { + 'index.md': ( + 'Hello\n\nBye' + ), + }, + { + 'es/index.md.po': { + 'Hello': 'Hola', + 'Bye': '', + }, + }, + { + 'languages': ['en', 'es'], + 'min_translated_messages': '50%', + }, + {}, + { + 'index.html': [ + '

Hello

', + '

Bye

', + ], + }, + ( + 'Excluding language "es". Translated 0% (0 of 3 messages)' + ' but required 50% at least.' + ), + ( + 'Excluding language "es". Translated ~33.33% (1 of 3 messages)' + ' but required 50% at least.' + ), + id='min_translated_messages=50%', + ), + pytest.param( + { + 'index.md': ( + 'Hello\n\nBye' + ), + }, + { + 'es/index.md.po': { + 'Hello': 'Hola', + 'Bye': '', + }, + }, + { + 'languages': ['en', 'es'], + 'min_translated_messages': 2, + }, + {}, + { + 'index.html': [ + '

Hello

', + '

Bye

', + ], + }, + ( + 'Excluding language "es". Translated 0 messages of' + ' 3 but required 2 translated messages at least.' + ), + ( + 'Excluding language "es". Translated 1 messages of' + ' 3 but required 2 translated messages at least.' + ), + id='min_translated_messages=2', + ), +) + + +@pytest.mark.parametrize( + ( + 'input_files_contents', + 'translations', + 'plugin_config', + 'additional_config', + 'expected_output_files', + 'expected_first_build_log', + 'expected_second_build_log', + ), + TESTS, +) +def test_navigation_and_page_building_plugins( + input_files_contents, + translations, + plugin_config, + additional_config, + expected_output_files, + expected_first_build_log, + expected_second_build_log, + mkdocs_build, +): + def check_translation_files_not_exists(context): + es_path = os.path.join(context['site_dir'], 'es') + es_index_path = os.path.join(es_path, 'index.html') + assert not os.path.exists(es_path) + assert not os.path.exists(es_index_path) + + mkdocs_log, plugin_log = mkdocs_build( + input_files_contents, + translations, + plugin_config, + additional_config, + expected_output_files, + callback_after_first_build=check_translation_files_not_exists, + allow_missing_translations=True, + ) + + # first build log + assert expected_first_build_log in plugin_log + + # second build log + assert expected_second_build_log in ( + plugin_log.split(expected_first_build_log)[-1] # after first build log + ) diff --git a/tests/test_plugins/test_api_documentation_building.py b/tests/test_plugins/test_api_documentation_building.py index be79ac6..63cf5b3 100644 --- a/tests/test_plugins/test_api_documentation_building.py +++ b/tests/test_plugins/test_api_documentation_building.py @@ -17,7 +17,7 @@ { 'es/index.md.po': { 'Hello': 'Hola', - 'Bye': 'Adios', + 'Bye': 'Adiós', 'Function documentation.': 'Documentación de función.', 'Value to check.': 'Valor a comprobar.', },