[master 2/6] Squashed 'translation-canary/' content from commit
5a45c19
by dashea
From: David Shea <dshea(a)redhat.com>
git-subtree-dir: translation-canary
git-subtree-split: 5a45c19764d4e380726813fad13eaa4721607f57
---
.gitignore | 3 +
Makefile | 11 ++
README.rst | 37 ++++++
tests/pylint/runpylint.py | 21 ++++
tests/unittests/test_translatable.py | 45 +++++++
tests/unittests/test_translated.py | 130 ++++++++++++++++++++
translation_canary/__init__.py | 0
translation_canary/translatable/__init__.py | 85 +++++++++++++
translation_canary/translatable/__main__.py | 33 ++++++
translation_canary/translatable/test_comment.py | 26 ++++
translation_canary/translatable/test_markup.py | 36 ++++++
translation_canary/translated/__init__.py | 144 +++++++++++++++++++++++
translation_canary/translated/__main__.py | 39 ++++++
translation_canary/translated/test_markup.py | 62 ++++++++++
translation_canary/translated/test_percentage.py | 42 +++++++
translation_canary/translated/test_usability.py | 28 +++++
xgettext_werror.sh | 46 ++++++++
17 files changed, 788 insertions(+)
create mode 100644 .gitignore
create mode 100644 Makefile
create mode 100644 README.rst
create mode 100755 tests/pylint/runpylint.py
create mode 100644 tests/unittests/test_translatable.py
create mode 100644 tests/unittests/test_translated.py
create mode 100644 translation_canary/__init__.py
create mode 100644 translation_canary/translatable/__init__.py
create mode 100644 translation_canary/translatable/__main__.py
create mode 100644 translation_canary/translatable/test_comment.py
create mode 100644 translation_canary/translatable/test_markup.py
create mode 100644 translation_canary/translated/__init__.py
create mode 100644 translation_canary/translated/__main__.py
create mode 100644 translation_canary/translated/test_markup.py
create mode 100644 translation_canary/translated/test_percentage.py
create mode 100644 translation_canary/translated/test_usability.py
create mode 100755 xgettext_werror.sh
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..acf2e36
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+__pycache__
+*.pyc
+tests/pylint/.pylint.d
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..c264b45
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,11 @@
+# This Makefile is just for running the tests
+
+all:
+ @echo "nothing to build"
+
+check:
+ PYTHONPATH=. tests/pylint/runpylint.py
+ python3 -m unittest discover tests/unittests
+
+ci:
+ $(MAKE) check
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..8d007d9
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,37 @@
+translation-canary
+-------------
+
+Translations can crash your program. Creating software for a wide audience
+means sending your strings away for translation, and giving up control of your
+strings means that strings with extralinguistic content can come back broken.
+No one is likely to even realize it until someone fires up your program in
+Hungarian and it crashes because Gtk bombed out on some busted markup, and the
+Hungarian speaker is sad, and you are sad, and everything is just the absolute
+worst.
+
+This is the canary in the translation coalmine.
+
+There are two parts to this project:
+
+translatable:
+ This contains checks on the strings to be submitted for translation. This
+ ensures that the content of the original strings marked for translation are
+ suitable for translation. These tests are run on the POT file before
+ uploading the POT or the updated PO files to the translators.
+
+translated:
+ This contains checks on the strings returned from the translators. This
+ ensures that the content of the translated strings won't break anything.
+ These tests are run on the source directory before creating a release.
+
+Both translatable and translated are run by running the module
+(e.g., `python3 -m translation_canary.translatable`) with the input file(s) as
+the argument.
+
+In addition to the python modules, this project contains xgettext_werror.sh, a
+wrapper for xgettext that treats warnings as errors. xgettext will print
+warnings as it extracts translatable strings from source files, and these
+warnings should be addressed instead of silently ignored as they scroll by in
+the build output. To use the script in a package that uses the gettext template
+files from autopoint or gettextize, set XGETTEXT=/path/to/xgettext_werror.sh in
+Makevars.
diff --git a/tests/pylint/runpylint.py b/tests/pylint/runpylint.py
new file mode 100755
index 0000000..edd1b59
--- /dev/null
+++ b/tests/pylint/runpylint.py
@@ -0,0 +1,21 @@
+#!/usr/bin/python3
+
+import sys
+from pocketlint import PocketLintConfig, PocketLinter
+
+class TranslationCanaryLintConfig(PocketLintConfig):
+ @property
+ def disabledOptions(self):
+ return [ "W9930", # Found interruptible system call %s
+ "I0011", # Locally disabling %s
+ ]
+
+ @property
+ def extraArgs(self):
+ return ["--init-import", "y"]
+
+if __name__ == "__main__":
+ conf = TranslationCanaryLintConfig()
+ linter = PocketLinter(conf)
+ rc = linter.run()
+ sys.exit(rc)
diff --git a/tests/unittests/test_translatable.py b/tests/unittests/test_translatable.py
new file mode 100644
index 0000000..3f5113e
--- /dev/null
+++ b/tests/unittests/test_translatable.py
@@ -0,0 +1,45 @@
+# Copyright (C) 2015 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties 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., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+# Red Hat Author(s): David Shea <dshea(a)redhat.com>
+
+import unittest
+from polib import POEntry
+
+from translation_canary.translatable.test_markup import test_markup
+from translation_canary.translatable.test_comment import test_comment
+
+class TestMarkup(unittest.TestCase):
+ def test_ok(self):
+ # no markup
+ test_markup(POEntry(msgid="test string"))
+
+ # internal markup
+ test_markup(POEntry(msgid="<b>test</b> string"))
+
+ def test_unnecessary_markup(self):
+ self.assertRaises(AssertionError, test_markup, POEntry(msgid="<b>test string</b>"))
+
+class TestComment(unittest.TestCase):
+ def test_ok(self):
+ # Perfectly fine string
+ test_comment(POEntry(msgid="Hello, I am a test string"))
+
+ # single-character string with a comment
+ test_comment(POEntry(msgid="c", comment="TRANSLATORS: 'c' to continue"))
+
+ def test_no_comment(self):
+ self.assertRaises(AssertionError, test_comment, POEntry(msgid="c"))
diff --git a/tests/unittests/test_translated.py b/tests/unittests/test_translated.py
new file mode 100644
index 0000000..c958e7a
--- /dev/null
+++ b/tests/unittests/test_translated.py
@@ -0,0 +1,130 @@
+# Copyright (C) 2015 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties 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., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public # License and may only be used or replicated with the express permission of # Red Hat, Inc.
+#
+# Red Hat Author(s): David Shea <dshea(a)redhat.com>
+
+import unittest
+import tempfile
+import warnings
+import shutil
+import os
+import polib
+
+from translation_canary.translated.test_markup import test_markup
+from translation_canary.translated.test_percentage import test_percentage
+from translation_canary.translated.test_usability import test_usability
+
+# convert a polib.MOFile into a NamedTemporaryFile
+def mofile(moobj):
+ f = tempfile.NamedTemporaryFile(suffix='.mo')
+ moobj.save(f.name)
+ return f
+
+# convenience function for creating a single-entry mofile
+def mofile_from_entry(*args, **kwargs):
+ moobj = polib.MOFile()
+ moobj.append(polib.MOEntry(*args, **kwargs))
+ return mofile(moobj)
+
+class TestMarkup(unittest.TestCase):
+ # I know, pylint, that's the point
+ # pylint: disable=invalid-markup
+
+ def test_ok(self):
+ # no markup
+ with mofile_from_entry(msgid="test string", msgstr="estay ingstray") as m:
+ test_markup(m.name)
+
+ # matching markup
+ with mofile_from_entry(msgid="<b>bold</b> string", msgstr="<b>oldbay</b> ingstray") as m:
+ test_markup(m.name)
+
+ # matching plural
+ with mofile_from_entry(msgid="%d <b>bold</b> string", msgid_plural="%d <b>bold</b> strings",
+ msgstr_plural={0: "%d <b>oldbay</b> ingstray", 1: "%d <b>oldbay</b> instrays"}) as m:
+ test_markup(m.name)
+
+ def test_missing(self):
+ with mofile_from_entry(msgid="<b>bold</b> string", msgstr="oldbay ingstray") as m:
+ self.assertRaises(AssertionError, test_markup, m.name)
+
+ def test_mismatch(self):
+ with mofile_from_entry(msgid="<b>bold</b> string", msgstr="<i>oldbay</i> ingstray") as m:
+ self.assertRaises(AssertionError, test_markup, m.name)
+
+ def test_typo(self):
+ with mofile_from_entry(msgid="<b>bold</b> string", msgstr="<boldbay</b> ingstray") as m:
+ self.assertRaises(AssertionError, test_markup, m.name)
+
+ def test_mismatch_plural(self):
+ with mofile_from_entry(msgid="%d <b>bold</b> string", msgid_plural="%d <b>bold</b> strings",
+ msgstr_plural={0: "%d <b>olbday</b> ingstray", 1: "%d oldbay ingstrays"}) as m:
+ self.assertRaises(AssertionError, test_markup, m.name)
+
+ def test_invalid(self):
+ # Tags themselves are valid, but the XML is not
+ with mofile_from_entry(msgid="<b>bold</b> string", msgstr="<b/>oldbay</b> ingstray") as m:
+ self.assertRaises(AssertionError, test_markup, m.name)
+
+class TestPercentage(unittest.TestCase):
+ # test_percentage actually looks at .po files, so the tests need to create
+ # both a .po and a .mo in self.tmpdir
+
+ def setUp(self):
+ self.tmpdir = tempfile.mkdtemp()
+ self.popath = os.path.join(self.tmpdir, "test.po")
+ self.mopath = os.path.join(self.tmpdir, "test.mo")
+
+ # Convert warnings into exceptions to make them easier to test for
+ warnings.simplefilter("error")
+ # polib throws a DeprecationWarnings so ignore that
+ warnings.simplefilter("default", DeprecationWarning)
+
+ def tearDown(self):
+ shutil.rmtree(self.tmpdir)
+ warnings.resetwarnings()
+
+ def test_ok(self):
+ # 100%
+ pofile = polib.POFile()
+ pofile.append(polib.POEntry(msgid="test string", msgstr="estay ingstray"))
+ pofile.save(self.popath)
+ pofile.save_as_mofile(self.mopath)
+ test_percentage(self.mopath)
+
+ def test_not_ok(self):
+ # 0%
+ pofile = polib.POFile()
+ pofile.append(polib.POEntry(msgid="test string", msgstr=""))
+ pofile.save(self.popath)
+ pofile.save_as_mofile(self.mopath)
+
+ self.assertRaises(Warning, test_percentage, self.mopath)
+
+class TestUsability(unittest.TestCase):
+ def test_ok(self):
+ # what lt's Plural-Forms is supposed to look like
+ moobj = polib.MOFile()
+ moobj.metadata["Plural-Forms"] = "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
+
+ with mofile(moobj) as m:
+ test_usability(m.name)
+
+ def test_busted_plural_forms(self):
+ # https://bugzilla.redhat.com/show_bug.cgi?id=1283599
+ moobj = polib.MOFile()
+ moobj.metadata["Plural-Forms"] = "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 or n%100>=20) ? 1 : 2)\n"
+
+ with mofile(moobj) as m:
+ self.assertRaises(Exception, test_usability, m.name)
diff --git a/translation_canary/__init__.py b/translation_canary/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/translation_canary/translatable/__init__.py b/translation_canary/translatable/__init__.py
new file mode 100644
index 0000000..c82da7a
--- /dev/null
+++ b/translation_canary/translatable/__init__.py
@@ -0,0 +1,85 @@
+# Framework for testing translatable strings
+#
+# Copyright (C) 2015 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties 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., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+# Red Hat Author(s): David Shea <dshea(a)redhat.com>
+
+"""
+Framework for running tests against translatable strings.
+
+Tests are loaded from modules in this directory. A test is any callable object
+within the module with a name that starts with 'test_'.
+
+Each test is called with a POEntry object as an argument. A test passes if it
+returns without raising an exception.
+"""
+
+try:
+ import polib
+except ImportError:
+ print("You need to install the python-polib package to read translations")
+ raise
+
+# Gather tests from this directory
+import pkgutil
+_tests = []
+for finder, mod_name, _ispkg in pkgutil.iter_modules(__path__):
+ # Skip __main__
+ if mod_name == "__main__":
+ continue
+
+ # Load the module
+ module = finder.find_module(mod_name).load_module()
+
+ # Look for attributes that start with 'test_' and add them to the test list
+ for attrname, attr in module.__dict__.items():
+ if attrname.startswith('test_') and callable(attr):
+ _tests.append(attr)
+
+def testString(poentry):
+ """Run all tests against the given translatable string.
+
+ :param polib.POEntry poentry: The PO file entry to test
+ :returns: whether the tests succeeded or not
+ :rtype: bool
+ """
+ success = True
+ for test in _tests:
+ try:
+ test(poentry)
+ except Exception as e: # pylint: disable=broad-except
+ success = False
+ print("%s failed on %s: %s" % (test.__name__, poentry.msgid, str(e)))
+
+ return success
+
+def testPOT(potfile):
+ """Run all tests against all entries in a POT file.
+
+ :param str potfile: The name of a .pot file to test
+ :return: whether the checks succeeded or not
+ :rtype: bool
+ """
+ success = True
+
+ parsed_pot = polib.pofile(potfile)
+
+ for entry in parsed_pot:
+ if not testString(entry):
+ success = False
+
+ return success
diff --git a/translation_canary/translatable/__main__.py b/translation_canary/translatable/__main__.py
new file mode 100644
index 0000000..905cc70
--- /dev/null
+++ b/translation_canary/translatable/__main__.py
@@ -0,0 +1,33 @@
+# Entry point for testing translatable strings
+#
+# Copyright (C) 2015 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties 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., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+# Red Hat Author(s): David Shea <dshea(a)redhat.com>
+
+import sys
+from . import testPOT
+
+if len(sys.argv) < 2:
+ print("Usage: translatable <POTfile>")
+ sys.exit(1)
+
+status = 0
+for potfile in sys.argv[1:]:
+ if not testPOT(potfile):
+ status = 1
+
+sys.exit(status)
diff --git a/translation_canary/translatable/test_comment.py b/translation_canary/translatable/test_comment.py
new file mode 100644
index 0000000..51410fb
--- /dev/null
+++ b/translation_canary/translatable/test_comment.py
@@ -0,0 +1,26 @@
+# Check that a string that needs a comment has one
+# # Copyright (C) 2015 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties 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., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+# Red Hat Author(s): David Shea <dshea(a)redhat.com>
+
+def test_comment(poentry):
+ # Single-character translatable strings (e.g., the 'c' of press c to
+ # continue) need some additional context in order to make sense. Make
+ # sure that they have it.
+
+ if len(poentry.msgid) == 1 and not poentry.comment:
+ raise AssertionError("Single-character string missing a comment.")
diff --git a/translation_canary/translatable/test_markup.py b/translation_canary/translatable/test_markup.py
new file mode 100644
index 0000000..fed02ed
--- /dev/null
+++ b/translation_canary/translatable/test_markup.py
@@ -0,0 +1,36 @@
+# Check that a string does not contain unnecessary Pango markup
+#
+# Copyright (C) 2015 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties 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., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+# Red Hat Author(s): David Shea <dshea(a)redhat.com>
+
+from pocketlint.pangocheck import is_markup, markup_necessary
+import xml.etree.ElementTree as ET
+
+def test_markup(poentry):
+ # Unnecessary markup is markup applied to an entire string, such as
+ # _("<b>Bold Text</b>"). This could be instead be translated as
+ # "<b>%s</b>" % _("Bold Text"), and then the translator doesn't have to see
+ # the markup at all.
+
+ if is_markup(poentry.msgid):
+ # Wrap the string in <markup> nodes, parse it, test it
+ # The markup is unescaped on purpose
+ # pylint: disable=unescaped-markup
+ tree = ET.fromstring("<markup>%s</markup>" % poentry.msgid)
+ if not markup_necessary(tree):
+ raise AssertionError("Unnecessary markup")
diff --git a/translation_canary/translated/__init__.py b/translation_canary/translated/__init__.py
new file mode 100644
index 0000000..5826478
--- /dev/null
+++ b/translation_canary/translated/__init__.py
@@ -0,0 +1,144 @@
+# Framework for testing translations
+#
+# Copyright (C) 2015 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties 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., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+# Red Hat Author(s): David Shea <dshea(a)redhat.com>
+
+"""
+Framework for running tests against translations.
+
+Tests are loaded from modules in this directory. A test is any callable object
+within the module with a name that starts with 'test_'.
+
+Each test is called with the name of .mo file to test as an argument. A test
+passes if it returns without raising an exception.
+"""
+
+import os, warnings
+
+_tests = []
+
+# Gather tests from this directory
+import pkgutil
+for finder, mod_name, _ispkg in pkgutil.iter_modules(__path__):
+ # Skip __main__
+ if mod_name == "__main__":
+ continue
+
+ # Load the module
+ module = finder.find_module(mod_name).load_module()
+
+ # Look for attributes that start with 'test_' and add them to the test list
+ for attrname, attr in module.__dict__.items():
+ if attrname.startswith('test_') and callable(attr):
+ _tests.append(attr)
+
+def _remove_lingua(linguas, language):
+ # Read in the LINGUAS file
+ with open(linguas, "rt") as f:
+ lingua_lines = f.readlines()
+
+ output_lines = []
+ for line in lingua_lines:
+ # Leave comments alone
+ if line.startswith('#'):
+ output_lines.append(line)
+ continue
+
+ # Split the line into a list of languages, remove the one we don't
+ # want, and put it back together
+ lingua_list = line.split()
+ lingua_list.remove(language)
+ output_lines.append(" ".join(lingua_list))
+
+ # Write LINGUAS back out
+ with open(linguas, "wt") as f:
+ f.writelines(output_lines)
+
+def testFile(mofile, prefix=None, releaseMode=False):
+ """Run all registered tests against the given .mo file.
+
+ If run in release mode, this function will always return true, and if
+ the mofile does not pass the tests the langauge will be removed.
+
+ :param str mofile: The .mo file name to check
+ :param str prefix: An optional directory prefix to strip from error messages
+ :return: whether the checks succeeded or not
+ :rtype: bool
+ """
+ success = True
+ for test in _tests:
+ # Don't print the tmpdir path in error messages
+ if prefix is not None and mofile.startswith(prefix):
+ moerror = mofile[len(prefix):]
+ else:
+ moerror = mofile
+
+ try:
+ with warnings.catch_warnings(record=True) as w:
+ test(mofile)
+
+ # Print any warnings collected
+ for warn in w:
+ print("%s warned on %s: %s" % (test.__name__, moerror, warn.message))
+ except Exception as e: # pylint: disable=broad-except
+ print("%s failed on %s: %s" % (test.__name__, moerror, str(e)))
+ if releaseMode:
+ # Remove the mo file and the po file it was built from
+ print("Removing %s" % mofile)
+ os.remove(mofile)
+
+ pofile = os.path.splitext(mofile)[0] + '.po'
+ print("Removing %s" % pofile)
+ os.remove(pofile)
+
+ # If there is a LINGUAS file in the po directory, remove the
+ # language from it
+ linguas = os.path.join(os.path.dirname(mofile), 'LINGUAS')
+ if os.path.exists(linguas):
+ language = os.path.splitext(os.path.basename(mofile))[0]
+ print("Removing %s from LINGUAS" % language)
+ _remove_lingua(linguas, language)
+
+ # No need to run the rest of the tests since we just killed the file
+ break
+ else:
+ success = False
+
+ return success
+
+def testSourceTree(srcdir, releaseMode=False):
+ """Runs all registered tests against all .mo files in the given directory.
+
+ If run in release mode, this function will always return True and the
+ languages that do not pass the tests will be removed.
+
+ :param str srcdir: The path to the source directory to check
+ :param bool releaseMode: whether to run in release mode
+ :return: whether the checks succeeded or not
+ :rtype: bool
+ """
+ success = True
+ srcdir = os.path.normpath(srcdir)
+
+ for dirpath, _dirnames, paths in os.walk(srcdir):
+ for mofile in (os.path.join(dirpath, path) for path in paths
+ if path.endswith('.mo') or path.endswith('.gmo')):
+ if not testFile(mofile, prefix=srcdir + "/", releaseMode=releaseMode):
+ success = False
+
+ return success
diff --git a/translation_canary/translated/__main__.py b/translation_canary/translated/__main__.py
new file mode 100644
index 0000000..0745425
--- /dev/null
+++ b/translation_canary/translated/__main__.py
@@ -0,0 +1,39 @@
+# Entry point for testing translations
+#
+# Copyright (C) 2015 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties 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., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+# Red Hat Author(s): David Shea <dshea(a)redhat.com>
+
+import sys, argparse
+from . import testSourceTree
+
+ap = argparse.ArgumentParser(description='Validate translated strings')
+ap.add_argument('--release', action='store_true', default=False,
+ help='Run in release mode')
+ap.add_argument('--test', dest='release', action='store_false',
+ help='Run in test mode')
+ap.add_argument('source_trees', metavar='SOURCE-TREE', nargs='+',
+ help='Source directory to test')
+
+args = ap.parse_args()
+
+status = 0
+for srcdir in args.source_trees:
+ if not testSourceTree(srcdir, args.release):
+ status = 1
+
+sys.exit(status)
diff --git a/translation_canary/translated/test_markup.py b/translation_canary/translated/test_markup.py
new file mode 100644
index 0000000..d395177
--- /dev/null
+++ b/translation_canary/translated/test_markup.py
@@ -0,0 +1,62 @@
+# Check translations of pango markup
+#
+# This will look for translatable strings that appear to contain markup and
+# check that the markup in the translation matches.
+#
+# Copyright (C) 2015 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties 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., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+# Red Hat Author(s): David Shea <dshea(a)redhat.com>
+
+try:
+ import polib
+except ImportError:
+ print("You need to install the python-polib package to read translations")
+ raise
+
+from pocketlint.pangocheck import is_markup, markup_match
+import xml.etree.ElementTree as ET
+
+def test_markup(mofile):
+ mo = polib.mofile(mofile)
+
+ for entry in mo.translated_entries():
+ if is_markup(entry.msgid):
+ # If this is a plural, check each of the plural translations
+ if entry.msgid_plural:
+ xlations = entry.msgstr_plural
+ else:
+ xlations = {None: entry.msgstr}
+
+ for plural_id, msgstr in xlations.items():
+ # Check if the markup is valid at all
+ try:
+ # pylint: disable=unescaped-markup
+ ET.fromstring('<markup>%s</markup>' % msgstr)
+ except ET.ParseError:
+ if entry.msgid_plural:
+ raise AssertionError("Invalid markup translation for %d translation of msgid %s" %
+ (plural_id, entry.msgid))
+ else:
+ raise AssertionError("Invalid markup translation for msgid %s" % entry.msgid)
+
+ # Check if the markup has the same number and kind of tags
+ if not markup_match(entry.msgid, msgstr):
+ if entry.msgid_plural:
+ raise AssertionError("Markup does not match for %d translation of msgid %s" %
+ (plural_id, entry.msgid))
+ else:
+ raise AssertionError("Markup does not match for msgid %s" % entry.msgid)
diff --git a/translation_canary/translated/test_percentage.py b/translation_canary/translated/test_percentage.py
new file mode 100644
index 0000000..914fd09
--- /dev/null
+++ b/translation_canary/translated/test_percentage.py
@@ -0,0 +1,42 @@
+# Check what percentage of strings a .mo translates
+#
+# This will reject translations that fall below a certain threshold of
+# translated strings.
+#
+# Copyright (C) 2015 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties 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., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+# Red Hat Author(s): David Shea <dshea(a)redhat.com>
+
+import os
+import warnings
+
+try:
+ import polib
+except ImportError:
+ print("You need to install the python-polib package to read translations")
+ raise
+
+threshold = 10
+
+def test_percentage(mofile):
+ # Open the .po file instead, which should be in the same location as the
+ # .mo file in the source archive
+ pofile = polib.pofile(os.path.splitext(mofile)[0] + '.po')
+ if pofile.percent_translated() < threshold:
+ # Issue a warning instead of an exception, since these should probably
+ # be handled on a case-by-case basis
+ warnings.warn("amount translated of %d%% below threshold of %d%%" % (pofile.percent_translated(), threshold))
diff --git a/translation_canary/translated/test_usability.py b/translation_canary/translated/test_usability.py
new file mode 100644
index 0000000..bc51bb9
--- /dev/null
+++ b/translation_canary/translated/test_usability.py
@@ -0,0 +1,28 @@
+# Check a .mo file for basic usability
+#
+# This will test that the file is well-formed and that the Plural-Forms value
+# is parseable
+#
+# Copyright (C) 2015 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties 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., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+# Red Hat Author(s): David Shea <dshea(a)redhat.com>
+
+import gettext
+
+def test_usability(mofile):
+ with open(mofile, "rb") as fp:
+ _t = gettext.GNUTranslations(fp=fp)
diff --git a/xgettext_werror.sh b/xgettext_werror.sh
new file mode 100755
index 0000000..36eefec
--- /dev/null
+++ b/xgettext_werror.sh
@@ -0,0 +1,46 @@
+#!/bin/sh -e
+#
+# xgettext_werror.sh: Run xgettext and actually do something with the warnings
+#
+# xgettext prints out warnings for certain problems in translatable strings,
+# such as format strings that cannot be translated due to position-based
+# parameters. These warnings generally indicate something that needs to be
+# addressed before the strings can be submitted for translation. This script
+# exits with a status of 1 so that the warnings are not ignored as they scroll
+# by in pages of build output.
+#
+# This script should be used in place of xgettext when rebuilding the .pot file,
+# e.g. by setting XGETTEXT in po/Makevars.
+#
+# Copyright (C) 2015 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties 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., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+# Red Hat Author(s): David Shea <dshea(a)redhat.com>
+
+returncode=0
+
+# Collect the output from xgettext. If xgettext fails, treat that as a failure
+# Make sure that "warning:" doesn't get translated
+xgettext_output="$(LC_MESSAGES=C xgettext "$@" 2>&1)" || returncode=$?
+
+# Look for warnings
+if echo "$xgettext_output" | fgrep -q "warning: "; then
+ returncode=1
+fi
+
+# Print the output and return
+echo "$xgettext_output"
+exit $returncode
--
To view this commit on github, visit https://github.com/rhinstaller/blivet/commit/48be3a48cfca5fd36e5f5e1c9c3d...
8 years, 5 months
[master 1/6] Squashed 'translation-canary/' content from commit
5a45c19
by dashea
From: David Shea <dshea(a)redhat.com>
git-subtree-dir: translation-canary
git-subtree-split: 5a45c19764d4e380726813fad13eaa4721607f57
---
.gitignore | 3 +
Makefile | 11 ++
README.rst | 37 ++++++
tests/pylint/runpylint.py | 21 ++++
tests/unittests/test_translatable.py | 45 +++++++
tests/unittests/test_translated.py | 130 ++++++++++++++++++++
translation_canary/__init__.py | 0
translation_canary/translatable/__init__.py | 85 +++++++++++++
translation_canary/translatable/__main__.py | 33 ++++++
translation_canary/translatable/test_comment.py | 26 ++++
translation_canary/translatable/test_markup.py | 36 ++++++
translation_canary/translated/__init__.py | 144 +++++++++++++++++++++++
translation_canary/translated/__main__.py | 39 ++++++
translation_canary/translated/test_markup.py | 62 ++++++++++
translation_canary/translated/test_percentage.py | 42 +++++++
translation_canary/translated/test_usability.py | 28 +++++
xgettext_werror.sh | 46 ++++++++
17 files changed, 788 insertions(+)
create mode 100644 .gitignore
create mode 100644 Makefile
create mode 100644 README.rst
create mode 100755 tests/pylint/runpylint.py
create mode 100644 tests/unittests/test_translatable.py
create mode 100644 tests/unittests/test_translated.py
create mode 100644 translation_canary/__init__.py
create mode 100644 translation_canary/translatable/__init__.py
create mode 100644 translation_canary/translatable/__main__.py
create mode 100644 translation_canary/translatable/test_comment.py
create mode 100644 translation_canary/translatable/test_markup.py
create mode 100644 translation_canary/translated/__init__.py
create mode 100644 translation_canary/translated/__main__.py
create mode 100644 translation_canary/translated/test_markup.py
create mode 100644 translation_canary/translated/test_percentage.py
create mode 100644 translation_canary/translated/test_usability.py
create mode 100755 xgettext_werror.sh
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..acf2e36
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+__pycache__
+*.pyc
+tests/pylint/.pylint.d
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..c264b45
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,11 @@
+# This Makefile is just for running the tests
+
+all:
+ @echo "nothing to build"
+
+check:
+ PYTHONPATH=. tests/pylint/runpylint.py
+ python3 -m unittest discover tests/unittests
+
+ci:
+ $(MAKE) check
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..8d007d9
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,37 @@
+translation-canary
+-------------
+
+Translations can crash your program. Creating software for a wide audience
+means sending your strings away for translation, and giving up control of your
+strings means that strings with extralinguistic content can come back broken.
+No one is likely to even realize it until someone fires up your program in
+Hungarian and it crashes because Gtk bombed out on some busted markup, and the
+Hungarian speaker is sad, and you are sad, and everything is just the absolute
+worst.
+
+This is the canary in the translation coalmine.
+
+There are two parts to this project:
+
+translatable:
+ This contains checks on the strings to be submitted for translation. This
+ ensures that the content of the original strings marked for translation are
+ suitable for translation. These tests are run on the POT file before
+ uploading the POT or the updated PO files to the translators.
+
+translated:
+ This contains checks on the strings returned from the translators. This
+ ensures that the content of the translated strings won't break anything.
+ These tests are run on the source directory before creating a release.
+
+Both translatable and translated are run by running the module
+(e.g., `python3 -m translation_canary.translatable`) with the input file(s) as
+the argument.
+
+In addition to the python modules, this project contains xgettext_werror.sh, a
+wrapper for xgettext that treats warnings as errors. xgettext will print
+warnings as it extracts translatable strings from source files, and these
+warnings should be addressed instead of silently ignored as they scroll by in
+the build output. To use the script in a package that uses the gettext template
+files from autopoint or gettextize, set XGETTEXT=/path/to/xgettext_werror.sh in
+Makevars.
diff --git a/tests/pylint/runpylint.py b/tests/pylint/runpylint.py
new file mode 100755
index 0000000..edd1b59
--- /dev/null
+++ b/tests/pylint/runpylint.py
@@ -0,0 +1,21 @@
+#!/usr/bin/python3
+
+import sys
+from pocketlint import PocketLintConfig, PocketLinter
+
+class TranslationCanaryLintConfig(PocketLintConfig):
+ @property
+ def disabledOptions(self):
+ return [ "W9930", # Found interruptible system call %s
+ "I0011", # Locally disabling %s
+ ]
+
+ @property
+ def extraArgs(self):
+ return ["--init-import", "y"]
+
+if __name__ == "__main__":
+ conf = TranslationCanaryLintConfig()
+ linter = PocketLinter(conf)
+ rc = linter.run()
+ sys.exit(rc)
diff --git a/tests/unittests/test_translatable.py b/tests/unittests/test_translatable.py
new file mode 100644
index 0000000..3f5113e
--- /dev/null
+++ b/tests/unittests/test_translatable.py
@@ -0,0 +1,45 @@
+# Copyright (C) 2015 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties 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., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+# Red Hat Author(s): David Shea <dshea(a)redhat.com>
+
+import unittest
+from polib import POEntry
+
+from translation_canary.translatable.test_markup import test_markup
+from translation_canary.translatable.test_comment import test_comment
+
+class TestMarkup(unittest.TestCase):
+ def test_ok(self):
+ # no markup
+ test_markup(POEntry(msgid="test string"))
+
+ # internal markup
+ test_markup(POEntry(msgid="<b>test</b> string"))
+
+ def test_unnecessary_markup(self):
+ self.assertRaises(AssertionError, test_markup, POEntry(msgid="<b>test string</b>"))
+
+class TestComment(unittest.TestCase):
+ def test_ok(self):
+ # Perfectly fine string
+ test_comment(POEntry(msgid="Hello, I am a test string"))
+
+ # single-character string with a comment
+ test_comment(POEntry(msgid="c", comment="TRANSLATORS: 'c' to continue"))
+
+ def test_no_comment(self):
+ self.assertRaises(AssertionError, test_comment, POEntry(msgid="c"))
diff --git a/tests/unittests/test_translated.py b/tests/unittests/test_translated.py
new file mode 100644
index 0000000..c958e7a
--- /dev/null
+++ b/tests/unittests/test_translated.py
@@ -0,0 +1,130 @@
+# Copyright (C) 2015 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties 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., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public # License and may only be used or replicated with the express permission of # Red Hat, Inc.
+#
+# Red Hat Author(s): David Shea <dshea(a)redhat.com>
+
+import unittest
+import tempfile
+import warnings
+import shutil
+import os
+import polib
+
+from translation_canary.translated.test_markup import test_markup
+from translation_canary.translated.test_percentage import test_percentage
+from translation_canary.translated.test_usability import test_usability
+
+# convert a polib.MOFile into a NamedTemporaryFile
+def mofile(moobj):
+ f = tempfile.NamedTemporaryFile(suffix='.mo')
+ moobj.save(f.name)
+ return f
+
+# convenience function for creating a single-entry mofile
+def mofile_from_entry(*args, **kwargs):
+ moobj = polib.MOFile()
+ moobj.append(polib.MOEntry(*args, **kwargs))
+ return mofile(moobj)
+
+class TestMarkup(unittest.TestCase):
+ # I know, pylint, that's the point
+ # pylint: disable=invalid-markup
+
+ def test_ok(self):
+ # no markup
+ with mofile_from_entry(msgid="test string", msgstr="estay ingstray") as m:
+ test_markup(m.name)
+
+ # matching markup
+ with mofile_from_entry(msgid="<b>bold</b> string", msgstr="<b>oldbay</b> ingstray") as m:
+ test_markup(m.name)
+
+ # matching plural
+ with mofile_from_entry(msgid="%d <b>bold</b> string", msgid_plural="%d <b>bold</b> strings",
+ msgstr_plural={0: "%d <b>oldbay</b> ingstray", 1: "%d <b>oldbay</b> instrays"}) as m:
+ test_markup(m.name)
+
+ def test_missing(self):
+ with mofile_from_entry(msgid="<b>bold</b> string", msgstr="oldbay ingstray") as m:
+ self.assertRaises(AssertionError, test_markup, m.name)
+
+ def test_mismatch(self):
+ with mofile_from_entry(msgid="<b>bold</b> string", msgstr="<i>oldbay</i> ingstray") as m:
+ self.assertRaises(AssertionError, test_markup, m.name)
+
+ def test_typo(self):
+ with mofile_from_entry(msgid="<b>bold</b> string", msgstr="<boldbay</b> ingstray") as m:
+ self.assertRaises(AssertionError, test_markup, m.name)
+
+ def test_mismatch_plural(self):
+ with mofile_from_entry(msgid="%d <b>bold</b> string", msgid_plural="%d <b>bold</b> strings",
+ msgstr_plural={0: "%d <b>olbday</b> ingstray", 1: "%d oldbay ingstrays"}) as m:
+ self.assertRaises(AssertionError, test_markup, m.name)
+
+ def test_invalid(self):
+ # Tags themselves are valid, but the XML is not
+ with mofile_from_entry(msgid="<b>bold</b> string", msgstr="<b/>oldbay</b> ingstray") as m:
+ self.assertRaises(AssertionError, test_markup, m.name)
+
+class TestPercentage(unittest.TestCase):
+ # test_percentage actually looks at .po files, so the tests need to create
+ # both a .po and a .mo in self.tmpdir
+
+ def setUp(self):
+ self.tmpdir = tempfile.mkdtemp()
+ self.popath = os.path.join(self.tmpdir, "test.po")
+ self.mopath = os.path.join(self.tmpdir, "test.mo")
+
+ # Convert warnings into exceptions to make them easier to test for
+ warnings.simplefilter("error")
+ # polib throws a DeprecationWarnings so ignore that
+ warnings.simplefilter("default", DeprecationWarning)
+
+ def tearDown(self):
+ shutil.rmtree(self.tmpdir)
+ warnings.resetwarnings()
+
+ def test_ok(self):
+ # 100%
+ pofile = polib.POFile()
+ pofile.append(polib.POEntry(msgid="test string", msgstr="estay ingstray"))
+ pofile.save(self.popath)
+ pofile.save_as_mofile(self.mopath)
+ test_percentage(self.mopath)
+
+ def test_not_ok(self):
+ # 0%
+ pofile = polib.POFile()
+ pofile.append(polib.POEntry(msgid="test string", msgstr=""))
+ pofile.save(self.popath)
+ pofile.save_as_mofile(self.mopath)
+
+ self.assertRaises(Warning, test_percentage, self.mopath)
+
+class TestUsability(unittest.TestCase):
+ def test_ok(self):
+ # what lt's Plural-Forms is supposed to look like
+ moobj = polib.MOFile()
+ moobj.metadata["Plural-Forms"] = "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
+
+ with mofile(moobj) as m:
+ test_usability(m.name)
+
+ def test_busted_plural_forms(self):
+ # https://bugzilla.redhat.com/show_bug.cgi?id=1283599
+ moobj = polib.MOFile()
+ moobj.metadata["Plural-Forms"] = "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 or n%100>=20) ? 1 : 2)\n"
+
+ with mofile(moobj) as m:
+ self.assertRaises(Exception, test_usability, m.name)
diff --git a/translation_canary/__init__.py b/translation_canary/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/translation_canary/translatable/__init__.py b/translation_canary/translatable/__init__.py
new file mode 100644
index 0000000..c82da7a
--- /dev/null
+++ b/translation_canary/translatable/__init__.py
@@ -0,0 +1,85 @@
+# Framework for testing translatable strings
+#
+# Copyright (C) 2015 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties 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., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+# Red Hat Author(s): David Shea <dshea(a)redhat.com>
+
+"""
+Framework for running tests against translatable strings.
+
+Tests are loaded from modules in this directory. A test is any callable object
+within the module with a name that starts with 'test_'.
+
+Each test is called with a POEntry object as an argument. A test passes if it
+returns without raising an exception.
+"""
+
+try:
+ import polib
+except ImportError:
+ print("You need to install the python-polib package to read translations")
+ raise
+
+# Gather tests from this directory
+import pkgutil
+_tests = []
+for finder, mod_name, _ispkg in pkgutil.iter_modules(__path__):
+ # Skip __main__
+ if mod_name == "__main__":
+ continue
+
+ # Load the module
+ module = finder.find_module(mod_name).load_module()
+
+ # Look for attributes that start with 'test_' and add them to the test list
+ for attrname, attr in module.__dict__.items():
+ if attrname.startswith('test_') and callable(attr):
+ _tests.append(attr)
+
+def testString(poentry):
+ """Run all tests against the given translatable string.
+
+ :param polib.POEntry poentry: The PO file entry to test
+ :returns: whether the tests succeeded or not
+ :rtype: bool
+ """
+ success = True
+ for test in _tests:
+ try:
+ test(poentry)
+ except Exception as e: # pylint: disable=broad-except
+ success = False
+ print("%s failed on %s: %s" % (test.__name__, poentry.msgid, str(e)))
+
+ return success
+
+def testPOT(potfile):
+ """Run all tests against all entries in a POT file.
+
+ :param str potfile: The name of a .pot file to test
+ :return: whether the checks succeeded or not
+ :rtype: bool
+ """
+ success = True
+
+ parsed_pot = polib.pofile(potfile)
+
+ for entry in parsed_pot:
+ if not testString(entry):
+ success = False
+
+ return success
diff --git a/translation_canary/translatable/__main__.py b/translation_canary/translatable/__main__.py
new file mode 100644
index 0000000..905cc70
--- /dev/null
+++ b/translation_canary/translatable/__main__.py
@@ -0,0 +1,33 @@
+# Entry point for testing translatable strings
+#
+# Copyright (C) 2015 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties 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., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+# Red Hat Author(s): David Shea <dshea(a)redhat.com>
+
+import sys
+from . import testPOT
+
+if len(sys.argv) < 2:
+ print("Usage: translatable <POTfile>")
+ sys.exit(1)
+
+status = 0
+for potfile in sys.argv[1:]:
+ if not testPOT(potfile):
+ status = 1
+
+sys.exit(status)
diff --git a/translation_canary/translatable/test_comment.py b/translation_canary/translatable/test_comment.py
new file mode 100644
index 0000000..51410fb
--- /dev/null
+++ b/translation_canary/translatable/test_comment.py
@@ -0,0 +1,26 @@
+# Check that a string that needs a comment has one
+# # Copyright (C) 2015 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties 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., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+# Red Hat Author(s): David Shea <dshea(a)redhat.com>
+
+def test_comment(poentry):
+ # Single-character translatable strings (e.g., the 'c' of press c to
+ # continue) need some additional context in order to make sense. Make
+ # sure that they have it.
+
+ if len(poentry.msgid) == 1 and not poentry.comment:
+ raise AssertionError("Single-character string missing a comment.")
diff --git a/translation_canary/translatable/test_markup.py b/translation_canary/translatable/test_markup.py
new file mode 100644
index 0000000..fed02ed
--- /dev/null
+++ b/translation_canary/translatable/test_markup.py
@@ -0,0 +1,36 @@
+# Check that a string does not contain unnecessary Pango markup
+#
+# Copyright (C) 2015 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties 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., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+# Red Hat Author(s): David Shea <dshea(a)redhat.com>
+
+from pocketlint.pangocheck import is_markup, markup_necessary
+import xml.etree.ElementTree as ET
+
+def test_markup(poentry):
+ # Unnecessary markup is markup applied to an entire string, such as
+ # _("<b>Bold Text</b>"). This could be instead be translated as
+ # "<b>%s</b>" % _("Bold Text"), and then the translator doesn't have to see
+ # the markup at all.
+
+ if is_markup(poentry.msgid):
+ # Wrap the string in <markup> nodes, parse it, test it
+ # The markup is unescaped on purpose
+ # pylint: disable=unescaped-markup
+ tree = ET.fromstring("<markup>%s</markup>" % poentry.msgid)
+ if not markup_necessary(tree):
+ raise AssertionError("Unnecessary markup")
diff --git a/translation_canary/translated/__init__.py b/translation_canary/translated/__init__.py
new file mode 100644
index 0000000..5826478
--- /dev/null
+++ b/translation_canary/translated/__init__.py
@@ -0,0 +1,144 @@
+# Framework for testing translations
+#
+# Copyright (C) 2015 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties 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., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+# Red Hat Author(s): David Shea <dshea(a)redhat.com>
+
+"""
+Framework for running tests against translations.
+
+Tests are loaded from modules in this directory. A test is any callable object
+within the module with a name that starts with 'test_'.
+
+Each test is called with the name of .mo file to test as an argument. A test
+passes if it returns without raising an exception.
+"""
+
+import os, warnings
+
+_tests = []
+
+# Gather tests from this directory
+import pkgutil
+for finder, mod_name, _ispkg in pkgutil.iter_modules(__path__):
+ # Skip __main__
+ if mod_name == "__main__":
+ continue
+
+ # Load the module
+ module = finder.find_module(mod_name).load_module()
+
+ # Look for attributes that start with 'test_' and add them to the test list
+ for attrname, attr in module.__dict__.items():
+ if attrname.startswith('test_') and callable(attr):
+ _tests.append(attr)
+
+def _remove_lingua(linguas, language):
+ # Read in the LINGUAS file
+ with open(linguas, "rt") as f:
+ lingua_lines = f.readlines()
+
+ output_lines = []
+ for line in lingua_lines:
+ # Leave comments alone
+ if line.startswith('#'):
+ output_lines.append(line)
+ continue
+
+ # Split the line into a list of languages, remove the one we don't
+ # want, and put it back together
+ lingua_list = line.split()
+ lingua_list.remove(language)
+ output_lines.append(" ".join(lingua_list))
+
+ # Write LINGUAS back out
+ with open(linguas, "wt") as f:
+ f.writelines(output_lines)
+
+def testFile(mofile, prefix=None, releaseMode=False):
+ """Run all registered tests against the given .mo file.
+
+ If run in release mode, this function will always return true, and if
+ the mofile does not pass the tests the langauge will be removed.
+
+ :param str mofile: The .mo file name to check
+ :param str prefix: An optional directory prefix to strip from error messages
+ :return: whether the checks succeeded or not
+ :rtype: bool
+ """
+ success = True
+ for test in _tests:
+ # Don't print the tmpdir path in error messages
+ if prefix is not None and mofile.startswith(prefix):
+ moerror = mofile[len(prefix):]
+ else:
+ moerror = mofile
+
+ try:
+ with warnings.catch_warnings(record=True) as w:
+ test(mofile)
+
+ # Print any warnings collected
+ for warn in w:
+ print("%s warned on %s: %s" % (test.__name__, moerror, warn.message))
+ except Exception as e: # pylint: disable=broad-except
+ print("%s failed on %s: %s" % (test.__name__, moerror, str(e)))
+ if releaseMode:
+ # Remove the mo file and the po file it was built from
+ print("Removing %s" % mofile)
+ os.remove(mofile)
+
+ pofile = os.path.splitext(mofile)[0] + '.po'
+ print("Removing %s" % pofile)
+ os.remove(pofile)
+
+ # If there is a LINGUAS file in the po directory, remove the
+ # language from it
+ linguas = os.path.join(os.path.dirname(mofile), 'LINGUAS')
+ if os.path.exists(linguas):
+ language = os.path.splitext(os.path.basename(mofile))[0]
+ print("Removing %s from LINGUAS" % language)
+ _remove_lingua(linguas, language)
+
+ # No need to run the rest of the tests since we just killed the file
+ break
+ else:
+ success = False
+
+ return success
+
+def testSourceTree(srcdir, releaseMode=False):
+ """Runs all registered tests against all .mo files in the given directory.
+
+ If run in release mode, this function will always return True and the
+ languages that do not pass the tests will be removed.
+
+ :param str srcdir: The path to the source directory to check
+ :param bool releaseMode: whether to run in release mode
+ :return: whether the checks succeeded or not
+ :rtype: bool
+ """
+ success = True
+ srcdir = os.path.normpath(srcdir)
+
+ for dirpath, _dirnames, paths in os.walk(srcdir):
+ for mofile in (os.path.join(dirpath, path) for path in paths
+ if path.endswith('.mo') or path.endswith('.gmo')):
+ if not testFile(mofile, prefix=srcdir + "/", releaseMode=releaseMode):
+ success = False
+
+ return success
diff --git a/translation_canary/translated/__main__.py b/translation_canary/translated/__main__.py
new file mode 100644
index 0000000..0745425
--- /dev/null
+++ b/translation_canary/translated/__main__.py
@@ -0,0 +1,39 @@
+# Entry point for testing translations
+#
+# Copyright (C) 2015 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties 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., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+# Red Hat Author(s): David Shea <dshea(a)redhat.com>
+
+import sys, argparse
+from . import testSourceTree
+
+ap = argparse.ArgumentParser(description='Validate translated strings')
+ap.add_argument('--release', action='store_true', default=False,
+ help='Run in release mode')
+ap.add_argument('--test', dest='release', action='store_false',
+ help='Run in test mode')
+ap.add_argument('source_trees', metavar='SOURCE-TREE', nargs='+',
+ help='Source directory to test')
+
+args = ap.parse_args()
+
+status = 0
+for srcdir in args.source_trees:
+ if not testSourceTree(srcdir, args.release):
+ status = 1
+
+sys.exit(status)
diff --git a/translation_canary/translated/test_markup.py b/translation_canary/translated/test_markup.py
new file mode 100644
index 0000000..d395177
--- /dev/null
+++ b/translation_canary/translated/test_markup.py
@@ -0,0 +1,62 @@
+# Check translations of pango markup
+#
+# This will look for translatable strings that appear to contain markup and
+# check that the markup in the translation matches.
+#
+# Copyright (C) 2015 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties 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., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+# Red Hat Author(s): David Shea <dshea(a)redhat.com>
+
+try:
+ import polib
+except ImportError:
+ print("You need to install the python-polib package to read translations")
+ raise
+
+from pocketlint.pangocheck import is_markup, markup_match
+import xml.etree.ElementTree as ET
+
+def test_markup(mofile):
+ mo = polib.mofile(mofile)
+
+ for entry in mo.translated_entries():
+ if is_markup(entry.msgid):
+ # If this is a plural, check each of the plural translations
+ if entry.msgid_plural:
+ xlations = entry.msgstr_plural
+ else:
+ xlations = {None: entry.msgstr}
+
+ for plural_id, msgstr in xlations.items():
+ # Check if the markup is valid at all
+ try:
+ # pylint: disable=unescaped-markup
+ ET.fromstring('<markup>%s</markup>' % msgstr)
+ except ET.ParseError:
+ if entry.msgid_plural:
+ raise AssertionError("Invalid markup translation for %d translation of msgid %s" %
+ (plural_id, entry.msgid))
+ else:
+ raise AssertionError("Invalid markup translation for msgid %s" % entry.msgid)
+
+ # Check if the markup has the same number and kind of tags
+ if not markup_match(entry.msgid, msgstr):
+ if entry.msgid_plural:
+ raise AssertionError("Markup does not match for %d translation of msgid %s" %
+ (plural_id, entry.msgid))
+ else:
+ raise AssertionError("Markup does not match for msgid %s" % entry.msgid)
diff --git a/translation_canary/translated/test_percentage.py b/translation_canary/translated/test_percentage.py
new file mode 100644
index 0000000..914fd09
--- /dev/null
+++ b/translation_canary/translated/test_percentage.py
@@ -0,0 +1,42 @@
+# Check what percentage of strings a .mo translates
+#
+# This will reject translations that fall below a certain threshold of
+# translated strings.
+#
+# Copyright (C) 2015 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties 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., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+# Red Hat Author(s): David Shea <dshea(a)redhat.com>
+
+import os
+import warnings
+
+try:
+ import polib
+except ImportError:
+ print("You need to install the python-polib package to read translations")
+ raise
+
+threshold = 10
+
+def test_percentage(mofile):
+ # Open the .po file instead, which should be in the same location as the
+ # .mo file in the source archive
+ pofile = polib.pofile(os.path.splitext(mofile)[0] + '.po')
+ if pofile.percent_translated() < threshold:
+ # Issue a warning instead of an exception, since these should probably
+ # be handled on a case-by-case basis
+ warnings.warn("amount translated of %d%% below threshold of %d%%" % (pofile.percent_translated(), threshold))
diff --git a/translation_canary/translated/test_usability.py b/translation_canary/translated/test_usability.py
new file mode 100644
index 0000000..bc51bb9
--- /dev/null
+++ b/translation_canary/translated/test_usability.py
@@ -0,0 +1,28 @@
+# Check a .mo file for basic usability
+#
+# This will test that the file is well-formed and that the Plural-Forms value
+# is parseable
+#
+# Copyright (C) 2015 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties 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., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+# Red Hat Author(s): David Shea <dshea(a)redhat.com>
+
+import gettext
+
+def test_usability(mofile):
+ with open(mofile, "rb") as fp:
+ _t = gettext.GNUTranslations(fp=fp)
diff --git a/xgettext_werror.sh b/xgettext_werror.sh
new file mode 100755
index 0000000..36eefec
--- /dev/null
+++ b/xgettext_werror.sh
@@ -0,0 +1,46 @@
+#!/bin/sh -e
+#
+# xgettext_werror.sh: Run xgettext and actually do something with the warnings
+#
+# xgettext prints out warnings for certain problems in translatable strings,
+# such as format strings that cannot be translated due to position-based
+# parameters. These warnings generally indicate something that needs to be
+# addressed before the strings can be submitted for translation. This script
+# exits with a status of 1 so that the warnings are not ignored as they scroll
+# by in pages of build output.
+#
+# This script should be used in place of xgettext when rebuilding the .pot file,
+# e.g. by setting XGETTEXT in po/Makevars.
+#
+# Copyright (C) 2015 Red Hat, Inc.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties 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., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+#
+# Red Hat Author(s): David Shea <dshea(a)redhat.com>
+
+returncode=0
+
+# Collect the output from xgettext. If xgettext fails, treat that as a failure
+# Make sure that "warning:" doesn't get translated
+xgettext_output="$(LC_MESSAGES=C xgettext "$@" 2>&1)" || returncode=$?
+
+# Look for warnings
+if echo "$xgettext_output" | fgrep -q "warning: "; then
+ returncode=1
+fi
+
+# Print the output and return
+echo "$xgettext_output"
+exit $returncode
--
To view this commit on github, visit https://github.com/rhinstaller/blivet/commit/c46218e190cfc051773ffed55f11...
8 years, 5 months
Updated: [rhinstaller/blivet/pulls/291 master] Translation tests
by dashea
This is more or less a template for applying translation-canary to non-autotools projects. This changes the source tarball created in "make release" from everything in git archive (+ ChangeLog + po/) to only the files selected by setup.py sdist. The things that are excluded in the sdist are not used to build the rpm, using sdist makes "make archive" consistent with "make local", and it makes it easier to modify what goes into the source distribution (with translation-canary).
These translation-canary subtree commits are starting to get kind of unwieldy. Maybe it would be better if I used --squash instead of merging the entire history?
--
To view this pull request on github, visit https://github.com/rhinstaller/blivet/pull/291
8 years, 5 months
[PATCH rhel6-branch] Add support for ipv6 to gateway boot option (#1177984)
by Radek Vykydal
Port from f15-branch
Resolves: rhbz#1177984
---
loader/loader.c | 31 +++++++++++++++++++++++++++++--
1 file changed, 29 insertions(+), 2 deletions(-)
diff --git a/loader/loader.c b/loader/loader.c
index 9c3d5a7..a8cbb1b 100644
--- a/loader/loader.c
+++ b/loader/loader.c
@@ -1104,8 +1104,35 @@ static void parseCmdLineFlags(struct loaderData_s * loaderData,
#endif
else if (!strncasecmp(argv[i], "netmask=", 8))
loaderData->netmask = strdup(argv[i] + 8);
- else if (!strncasecmp(argv[i], "gateway=", 8))
- loaderData->gateway = strdup(argv[i] + 8);
+ else if (!strncasecmp(argv[i], "gateway=", 8)) {
+ char *gateway = strdup(argv[i] + 8);
+ int rc;
+ struct in_addr addr;
+#ifdef ENABLE_IPV6
+ struct in6_addr addr6;
+#endif
+
+ if ((rc = inet_pton(AF_INET, gateway, &addr)) == 1) {
+ loaderData->gateway = gateway;
+ } else if (rc == 0) {
+#ifdef ENABLE_IPV6
+ if ((rc = inet_pton(AF_INET6, gateway, &addr6)) == 1) {
+ loaderData->gateway6 = gateway;
+ } else if (rc == 0) {
+#endif
+ logMessage(WARNING,
+ "invalid address in boot option gateway");
+#ifdef ENABLE_IPV6
+ } else {
+ logMessage(ERROR, "%s (%d): %s", __func__, __LINE__,
+ strerror(errno));
+ }
+#endif
+ } else {
+ logMessage(ERROR, "%s (%d): %s", __func__, __LINE__,
+ strerror(errno));
+ }
+ }
else if (!strncasecmp(argv[i], "dns=", 4))
loaderData->dns = strdup(argv[i] + 4);
else if (!strncasecmp(argv[i], "ethtool=", 8))
--
2.4.3
8 years, 5 months
[rhel6-branch 1/1] Fix regression in lvm snapshot detection.
(#1295897)
by dashea
From: David Lehman <dlehman(a)redhat.com>
---
storage/devicetree.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/storage/devicetree.py b/storage/devicetree.py
index f0733f0..23f6475 100644
--- a/storage/devicetree.py
+++ b/storage/devicetree.py
@@ -1594,7 +1594,7 @@ def addLV(lv_name, lv_uuid, lv_size, lv_attr, lv_type):
if origin_name.endswith("_vorigin]"):
log.info("snapshot volume '%s' has vorigin" % name)
lv_kwargs["vorigin"] = True
- vg_device.voriginSnapshots[lv_name] = lv_sizes[index]
+ vg_device.voriginSnapshots[lv_name] = lv_size
return
else:
origin_device_name = "%s-%s" % (vg_name, origin_name)
@@ -1611,8 +1611,8 @@ def addLV(lv_name, lv_uuid, lv_size, lv_attr, lv_type):
return
log.debug("adding %dMB to %s snapshot total"
- % (lv_sizes[index], origin.name))
- origin.snapshotSpace += lv_sizes[index]
+ % (lv_size, origin.name))
+ origin.snapshotSpace += lv_size
origin.snapshots.append(lv_name)
return
elif lv_attr[0] == 'v':
--
To view this commit on github, visit https://github.com/rhinstaller/anaconda/commit/a9a5377149d931b921513592f9...
8 years, 5 months
Re: [rhinstaller/initial-setup/pulls/5 master] Make Initial Setup to
run in fullscreen mode. RHBZ #1267203
by dashea
Thanks a ton for the reply. Would like to contribute more to anaconda :).
I will try to change the title to blank. But the header bar shown is having
a close button also, which looks weird.
If the initial setup is run after booting into rhel (python -m
initial_setup), the title bar is hidden, but why it's not hidden during
actual initial setup?
On Jan 5, 2016 7:34 PM, "David Shea" <notifications(a)github.com> wrote:
> In anaconda, we found full screen windows to be bug-prone. Depending on
> the combination of window manager and video driver, the full screen window
> might flicker, or be rendered all in black, or just not appear at all. The
> window already has hide-titlebar-when-maximized set to true to hide the
> title in window managers that support this, and for window managers that do
> not it would be better to just set the title to something else.
>
> —
> Reply to this email directly or view it on GitHub
> <https://github.com/rhinstaller/initial-setup/pull/5#issuecomment-169009495>
> .
>
--
To view this pull request on github, visit https://github.com/rhinstaller/initial-setup/pull/5
8 years, 5 months