# # Copyright (C) 2015 Red Hat, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . """ Common classes and functions for checking glade files. Glade file tests should provide a python file containing a subclass of GladeTest, below. When nose is run with GladePlugin, each GladeTest implementation will be run against each glade file. """ # Print more helpful messages before raising ImportError try: from lxml import etree except ImportError: print("No module named lxml, you need to install the python3-lxml package") raise try: from pocketlint.translatepo import translate_all except ImportError: print("Unable to load po translation module. You may need to install python3-polib") raise from abc import ABCMeta, abstractmethod import os import unittest import copy import nose from filelist import testfilelist import logging log = logging.getLogger('nose.plugins.glade') class GladeTest(unittest.TestCase, metaclass=ABCMeta): """A framework for checking glade files. Subclasses must implement the checkGlade method, which will be run for each glade file that is part of the test. The unittest assert* methods are available. If checkGlade returns without raising an exception it is considered to pass. If the translatable property is True and --translate was provided on the command line, checkGlade will also be called with translated versions of each glade file. """ translatable = False def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Set by the plugin in prepareTestCase, since that's easier than # trying to override how this object is created. self.glade_trees = [] self.translated_trees = {} @abstractmethod def checkGlade(self, tree): """Check a parsed glade file. :param etree.ElementTree tree: The parsed glade file """ pass def test_glade_file(self): """Run checkGlade for each glade file.""" for tree in self.glade_trees: with self.subTest(glade_file=tree.getroot().base): self.checkGlade(tree) if self.translatable: for lang, trees in self.translated_trees.items(): for tree in trees: with self.subTest(glade_file=tree.getroot().base, lang=lang): self.checkGlade(tree) class GladePlugin(nose.plugins.Plugin): name = "glade" enabled = True def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # These are filled during configure(), after we've decided what files # to check and whether to translate them. # Translations are a dict of {'lang': [list of trees]} self.glade_trees = [] self.translated_trees = {} def options(self, parser, env): # Do not call the superclass options() to skip setting up the # enable/disable options. parser.add_option("--glade-file", action="append", help="Glade file(s) to test. If none specified, all files will be tested") parser.add_option("--notranslate", dest="translate", action="store_false", default=False, help="Do not test translations of glade files") parser.add_option("--translate", action="store_true", help="Test glade files with translations") parser.add_option("--podir", action="store", type=str, default=os.environ.get('top_srcdir', '.') + "/po", metavar="PODIR", help="Directory containing .po files") def configure(self, options, conf): super().configure(options, conf) # If no glade files were specified, find all of them if options.glade_file: glade_files = options.glade_file else: glade_files = testfilelist(lambda x: x.endswith('.glade')) # Parse all of the glade files log.info("Parsing glade files...") for glade_file in glade_files: self.glade_trees.append(etree.parse(glade_file)) if options.translate: log.info("Loading translations...") podicts = translate_all(options.podir) # Loop over each available language for lang, langmap in podicts.items(): self.translated_trees[lang] = [] # For each language, loop over the parsed glade files for tree in self.glade_trees: # Make a copy of the tree to translate and save it to # the list for this language tree = copy.deepcopy(tree) self.translated_trees[lang].append(tree) # Save the language as an attribute of the root of the tree tree.getroot().set("lang", lang) # Look for all properties with translatable=yes and translate them for translatable in tree.xpath('//property[@translatable="yes"]'): try: xlated_text = langmap.get(translatable.text, context=translatable.get('context'))[0] # Add the untranslated text as an attribute to this node translatable.set("original_text", translatable.text) # Replace the actual text translatable.text = xlated_text except KeyError: # No translation available for this string in this language pass def prepareTestCase(self, testcase): # Add the glade files to the GladeTest object testcase.test.glade_trees = self.glade_trees testcase.test.translated_trees = self.translated_trees def describeTest(self, testcase): # Return the first line of the doc string on checkGlade instead # of the string for test_glade_file. If there is no doc string, # return the name of the class. doc = testcase.test.checkGlade.__doc__ if doc: return doc.strip().split("\n")[0].strip() else: return testcase.test.__class__.__name__ def wantClass(self, cls): # Make sure we grab all the GladeTest subclasses, and only GladeTest # subclasses, regardless of name. return issubclass(cls, GladeTest) and cls != GladeTest