← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~cjwatson/launchpad-buildd/pottery-tests into lp:launchpad-buildd

 

Colin Watson has proposed merging lp:~cjwatson/launchpad-buildd/pottery-tests into lp:launchpad-buildd.

Commit message:
Add tests for lpbuildd.pottery, extracted from Launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad-buildd/pottery-tests/+merge/323843

I think these were accidentally left in the Launchpad tree when the code under test was extracted, and they really don't belong there.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad-buildd/pottery-tests into lp:launchpad-buildd.
=== modified file 'Makefile'
--- Makefile	2015-07-31 11:54:07 +0000
+++ Makefile	2017-05-10 11:28:32 +0000
@@ -21,12 +21,14 @@
 .PHONY: all clean deb
 
 PYTHON=python
-# NB: for this to pass, you must have txfixtures, lazr.restful, and lp on your pythonpath
-# already.  lp is not packaged so this is not enforced as a build time
-# dependency. In practice you probably just want to run this with PYTHON=bin/py from 
-# a Launchpad checkout.
+# NB: for this to pass, you must have txfixtures on your pythonpath already.
+# txfixtures is not packaged so this is not enforced as a build time
+# dependency.  In practice you probably just want to run this with
+# PYTHON=bin/py from a Launchpad checkout.
 check:
 	PYTHONPATH=$(PWD):$(PYTHONPATH) $(PYTHON) -m testtools.run -v \
+		   lpbuildd.pottery.tests.test_generate_translation_templates \
+		   lpbuildd.pottery.tests.test_intltool \
 		   lpbuildd.tests.test_binarypackage \
 		   lpbuildd.tests.test_buildd_slave \
 		   lpbuildd.tests.test_buildrecipe \

=== modified file 'debian/changelog'
--- debian/changelog	2017-02-10 14:55:42 +0000
+++ debian/changelog	2017-05-10 11:28:32 +0000
@@ -1,3 +1,9 @@
+launchpad-buildd (143) UNRELEASED; urgency=medium
+
+  * Add tests for lpbuildd.pottery, extracted from Launchpad.
+
+ -- Colin Watson <cjwatson@xxxxxxxxxx>  Wed, 10 May 2017 12:26:11 +0100
+
 launchpad-buildd (142) trusty; urgency=medium
 
   * lpbuildd.binarypackage: Pass DEB_BUILD_OPTIONS=noautodbgsym if we have

=== modified file 'lpbuildd/pottery/intltool.py'
--- lpbuildd/pottery/intltool.py	2016-12-09 18:04:00 +0000
+++ lpbuildd/pottery/intltool.py	2017-05-10 11:28:32 +0000
@@ -235,8 +235,11 @@
     """Represent a config file and return variables defined in it."""
 
     def __init__(self, file_or_name):
-        with open(file_or_name) as conf_file:
-            self.content = conf_file.read()
+        if isinstance(file_or_name, str):
+            with open(file_or_name) as conf_file:
+                self.content = conf_file.read()
+        else:
+            self.content = file_or_name.read()
 
     def _stripQuotes(self, identifier):
         """Strip surrounding quotes from `identifier`, if present.

=== added directory 'lpbuildd/pottery/tests'
=== added file 'lpbuildd/pottery/tests/__init__.py'
=== added file 'lpbuildd/pottery/tests/dummy_templates.tar.gz'
Binary files lpbuildd/pottery/tests/dummy_templates.tar.gz	1970-01-01 00:00:00 +0000 and lpbuildd/pottery/tests/dummy_templates.tar.gz	2017-05-10 11:28:32 +0000 differ
=== added directory 'lpbuildd/pottery/tests/pottery_test_data'
=== added file 'lpbuildd/pottery/tests/pottery_test_data/intltool_POTFILES_in_1.tar.bz2'
Binary files lpbuildd/pottery/tests/pottery_test_data/intltool_POTFILES_in_1.tar.bz2	1970-01-01 00:00:00 +0000 and lpbuildd/pottery/tests/pottery_test_data/intltool_POTFILES_in_1.tar.bz2	2017-05-10 11:28:32 +0000 differ
=== added file 'lpbuildd/pottery/tests/pottery_test_data/intltool_POTFILES_in_2.tar.bz2'
Binary files lpbuildd/pottery/tests/pottery_test_data/intltool_POTFILES_in_2.tar.bz2	1970-01-01 00:00:00 +0000 and lpbuildd/pottery/tests/pottery_test_data/intltool_POTFILES_in_2.tar.bz2	2017-05-10 11:28:32 +0000 differ
=== added file 'lpbuildd/pottery/tests/pottery_test_data/intltool_domain_base.tar.bz2'
Binary files lpbuildd/pottery/tests/pottery_test_data/intltool_domain_base.tar.bz2	1970-01-01 00:00:00 +0000 and lpbuildd/pottery/tests/pottery_test_data/intltool_domain_base.tar.bz2	2017-05-10 11:28:32 +0000 differ
=== added file 'lpbuildd/pottery/tests/pottery_test_data/intltool_domain_configure_ac.tar.bz2'
Binary files lpbuildd/pottery/tests/pottery_test_data/intltool_domain_configure_ac.tar.bz2	1970-01-01 00:00:00 +0000 and lpbuildd/pottery/tests/pottery_test_data/intltool_domain_configure_ac.tar.bz2	2017-05-10 11:28:32 +0000 differ
=== added file 'lpbuildd/pottery/tests/pottery_test_data/intltool_domain_configure_in.tar.bz2'
Binary files lpbuildd/pottery/tests/pottery_test_data/intltool_domain_configure_in.tar.bz2	1970-01-01 00:00:00 +0000 and lpbuildd/pottery/tests/pottery_test_data/intltool_domain_configure_in.tar.bz2	2017-05-10 11:28:32 +0000 differ
=== added file 'lpbuildd/pottery/tests/pottery_test_data/intltool_domain_configure_in_substitute_version.tar.bz2'
Binary files lpbuildd/pottery/tests/pottery_test_data/intltool_domain_configure_in_substitute_version.tar.bz2	1970-01-01 00:00:00 +0000 and lpbuildd/pottery/tests/pottery_test_data/intltool_domain_configure_in_substitute_version.tar.bz2	2017-05-10 11:28:32 +0000 differ
=== added file 'lpbuildd/pottery/tests/pottery_test_data/intltool_domain_makefile_in_in.tar.bz2'
Binary files lpbuildd/pottery/tests/pottery_test_data/intltool_domain_makefile_in_in.tar.bz2	1970-01-01 00:00:00 +0000 and lpbuildd/pottery/tests/pottery_test_data/intltool_domain_makefile_in_in.tar.bz2	2017-05-10 11:28:32 +0000 differ
=== added file 'lpbuildd/pottery/tests/pottery_test_data/intltool_domain_makefile_in_in_substitute.tar.bz2'
Binary files lpbuildd/pottery/tests/pottery_test_data/intltool_domain_makefile_in_in_substitute.tar.bz2	1970-01-01 00:00:00 +0000 and lpbuildd/pottery/tests/pottery_test_data/intltool_domain_makefile_in_in_substitute.tar.bz2	2017-05-10 11:28:32 +0000 differ
=== added file 'lpbuildd/pottery/tests/pottery_test_data/intltool_domain_makefile_in_in_substitute_broken.tar.bz2'
Binary files lpbuildd/pottery/tests/pottery_test_data/intltool_domain_makefile_in_in_substitute_broken.tar.bz2	1970-01-01 00:00:00 +0000 and lpbuildd/pottery/tests/pottery_test_data/intltool_domain_makefile_in_in_substitute_broken.tar.bz2	2017-05-10 11:28:32 +0000 differ
=== added file 'lpbuildd/pottery/tests/pottery_test_data/intltool_domain_makefile_in_in_substitute_same_file.tar.bz2'
Binary files lpbuildd/pottery/tests/pottery_test_data/intltool_domain_makefile_in_in_substitute_same_file.tar.bz2	1970-01-01 00:00:00 +0000 and lpbuildd/pottery/tests/pottery_test_data/intltool_domain_makefile_in_in_substitute_same_file.tar.bz2	2017-05-10 11:28:32 +0000 differ
=== added file 'lpbuildd/pottery/tests/pottery_test_data/intltool_domain_makefile_in_in_substitute_same_name.tar.bz2'
Binary files lpbuildd/pottery/tests/pottery_test_data/intltool_domain_makefile_in_in_substitute_same_name.tar.bz2	1970-01-01 00:00:00 +0000 and lpbuildd/pottery/tests/pottery_test_data/intltool_domain_makefile_in_in_substitute_same_name.tar.bz2	2017-05-10 11:28:32 +0000 differ
=== added file 'lpbuildd/pottery/tests/pottery_test_data/intltool_domain_makevars.tar.bz2'
Binary files lpbuildd/pottery/tests/pottery_test_data/intltool_domain_makevars.tar.bz2	1970-01-01 00:00:00 +0000 and lpbuildd/pottery/tests/pottery_test_data/intltool_domain_makevars.tar.bz2	2017-05-10 11:28:32 +0000 differ
=== added file 'lpbuildd/pottery/tests/pottery_test_data/intltool_full_ok.tar.bz2'
Binary files lpbuildd/pottery/tests/pottery_test_data/intltool_full_ok.tar.bz2	1970-01-01 00:00:00 +0000 and lpbuildd/pottery/tests/pottery_test_data/intltool_full_ok.tar.bz2	2017-05-10 11:28:32 +0000 differ
=== added file 'lpbuildd/pottery/tests/pottery_test_data/intltool_single_ok.tar.bz2'
Binary files lpbuildd/pottery/tests/pottery_test_data/intltool_single_ok.tar.bz2	1970-01-01 00:00:00 +0000 and lpbuildd/pottery/tests/pottery_test_data/intltool_single_ok.tar.bz2	2017-05-10 11:28:32 +0000 differ
=== added file 'lpbuildd/pottery/tests/test_generate_translation_templates.py'
--- lpbuildd/pottery/tests/test_generate_translation_templates.py	1970-01-01 00:00:00 +0000
+++ lpbuildd/pottery/tests/test_generate_translation_templates.py	2017-05-10 11:28:32 +0000
@@ -0,0 +1,159 @@
+# Copyright 2010-2017 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+__metaclass__ = type
+
+import os
+from StringIO import StringIO
+import subprocess
+import sys
+import tarfile
+
+from bzrlib.bzrdir import BzrDir
+from bzrlib.generate_ids import (
+    gen_file_id,
+    gen_root_id,
+    )
+from bzrlib.transform import (
+    ROOT_PARENT,
+    TransformPreview,
+    )
+from fixtures import (
+    EnvironmentVariable,
+    TempDir,
+    )
+from testtools import TestCase
+from testtools.matchers import (
+    Equals,
+    MatchesSetwise,
+    )
+
+from lpbuildd import pottery
+from lpbuildd.pottery.generate_translation_templates import (
+    GenerateTranslationTemplates,
+    )
+from lpbuildd.tests.fakeslave import FakeMethod
+
+
+class TestGenerateTranslationTemplates(TestCase):
+    """Test generate-translation-templates script."""
+
+    result_name = "translation-templates.tar.gz"
+
+    def test_getBranch_url(self):
+        # If passed a branch URL, the template generation script will
+        # check out that branch into a directory called "source-tree."
+        branch_url = 'lp://~my/translation/branch'
+
+        generator = GenerateTranslationTemplates(
+            branch_url, self.result_name, self.useFixture(TempDir()).path,
+            log_file=StringIO())
+        generator._checkout = FakeMethod()
+        generator._getBranch()
+
+        self.assertEqual(1, generator._checkout.call_count)
+        self.assertTrue(generator.branch_dir.endswith('source-tree'))
+
+    def test_getBranch_dir(self):
+        # If passed a branch directory, the template generation script
+        # works directly in that directory.
+        branch_dir = '/home/me/branch'
+
+        generator = GenerateTranslationTemplates(
+            branch_dir, self.result_name, self.useFixture(TempDir()).path,
+            log_file=StringIO())
+        generator._checkout = FakeMethod()
+        generator._getBranch()
+
+        self.assertEqual(0, generator._checkout.call_count)
+        self.assertEqual(branch_dir, generator.branch_dir)
+
+    def _createBranch(self, content_map=None):
+        """Create a working branch.
+
+        :param content_map: optional dict mapping file names to file
+            contents.  Each of these files with their contents will be
+            written to the branch.  Currently only supports writing files at
+            the root directory of the branch.
+
+        :return: a tuple of a fresh bzr branch and its URL.
+        """
+        branch_url = 'file://' + self.useFixture(TempDir()).path
+        branch = BzrDir.create_branch_convenience(branch_url)
+
+        if content_map is not None:
+            branch.lock_write()
+            try:
+                revision_tree = branch.basis_tree()
+                transform_preview = TransformPreview(revision_tree)
+                try:
+                    root_id = transform_preview.new_directory(
+                        '', ROOT_PARENT, gen_root_id())
+                    for name, contents in content_map.iteritems():
+                        file_id = gen_file_id(name)
+                        transform_preview.new_file(
+                            name, root_id, [contents], file_id=file_id)
+                    committer_id = 'Committer <committer@xxxxxxxxxxx>'
+                    with EnvironmentVariable('BZR_EMAIL', committer_id):
+                        transform_preview.commit(
+                            branch, 'Populating branch.',
+                            committer=committer_id)
+                finally:
+                    transform_preview.finalize()
+            finally:
+                branch.unlock()
+
+        return branch, branch_url
+
+    def test_getBranch_bzr(self):
+        # _getBranch can retrieve branch contents from a branch URL.
+        bzr_home = self.useFixture(TempDir()).path
+        self.useFixture(EnvironmentVariable('BZR_HOME', bzr_home))
+        self.useFixture(EnvironmentVariable('BZR_EMAIL'))
+        self.useFixture(EnvironmentVariable('EMAIL'))
+
+        marker_text = "Ceci n'est pas cet branch."
+        branch, branch_url = self._createBranch({'marker.txt': marker_text})
+
+        generator = GenerateTranslationTemplates(
+            branch_url, self.result_name, self.useFixture(TempDir()).path,
+            log_file=StringIO())
+        generator._getBranch()
+
+        marker_path = os.path.join(generator.branch_dir, 'marker.txt')
+        with open(marker_path) as marker_file:
+            self.assertEqual(marker_text, marker_file.read())
+
+    def test_templates_tarball(self):
+        # Create a tarball from pot files.
+        workdir = self.useFixture(TempDir()).path
+        branchdir = os.path.join(workdir, 'branchdir')
+        dummy_tar = os.path.join(
+            os.path.dirname(__file__), 'dummy_templates.tar.gz')
+        with tarfile.open(dummy_tar, 'r|*') as tar:
+            tar.extractall(branchdir)
+            potnames = [
+                member.name
+                for member in tar.getmembers() if not member.isdir()]
+
+        generator = GenerateTranslationTemplates(
+            branchdir, self.result_name, workdir, log_file=StringIO())
+        generator._getBranch()
+        generator._makeTarball(potnames)
+        result_path = os.path.join(workdir, self.result_name)
+        with tarfile.open(result_path, 'r|*') as tar:
+            tarnames = tar.getnames()
+        self.assertThat(tarnames, MatchesSetwise(*(map(Equals, potnames))))
+
+    def test_script(self):
+        tempdir = self.useFixture(TempDir()).path
+        workdir = self.useFixture(TempDir()).path
+        command = [
+            sys.executable,
+            os.path.join(
+                os.path.dirname(pottery.__file__),
+                'generate_translation_templates.py'),
+            tempdir, self.result_name, workdir]
+        retval = subprocess.call(
+            command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        self.assertEqual(0, retval)

=== added file 'lpbuildd/pottery/tests/test_intltool.py'
--- lpbuildd/pottery/tests/test_intltool.py	1970-01-01 00:00:00 +0000
+++ lpbuildd/pottery/tests/test_intltool.py	2017-05-10 11:28:32 +0000
@@ -0,0 +1,513 @@
+# Copyright 2009-2017 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+__metaclass__ = type
+
+import errno
+import os
+from StringIO import StringIO
+import tarfile
+from textwrap import dedent
+
+from fixtures import TempDir
+from testtools import TestCase
+from testtools.matchers import (
+    Equals,
+    MatchesSetwise,
+    )
+
+from lpbuildd.pottery.intltool import (
+    check_potfiles_in,
+    ConfigFile,
+    find_intltool_dirs,
+    find_potfiles_in,
+    generate_pot,
+    generate_pots,
+    get_translation_domain,
+    )
+from lpbuildd.tests.fakeslave import FakeMethod
+
+
+class SetupTestPackageMixin:
+
+    test_data_dir = "pottery_test_data"
+
+    def prepare_package(self, packagename, buildfiles=None):
+        """Unpack the specified package in a temporary directory.
+
+        Change into the package's directory.
+
+        :param packagename: The name of the package to prepare.
+        :param buildfiles: A dictionary of path:content describing files to
+            add to the package.
+        """
+        # First build the path for the package.
+        cwd = os.getcwd()
+        packagepath = os.path.join(
+            cwd, os.path.dirname(__file__),
+            self.test_data_dir, packagename + ".tar.bz2")
+        # Then change into the temporary directory and unpack it.
+        os.chdir(self.useFixture(TempDir()).path)
+        self.addCleanup(os.chdir, cwd)
+        with tarfile.open(packagepath, "r|bz2") as tar:
+            tar.extractall()
+        os.chdir(packagename)
+
+        if buildfiles is None:
+            return
+
+        # Add files as requested.
+        for path, content in buildfiles.items():
+            directory = os.path.dirname(path)
+            if directory != '':
+                try:
+                    os.makedirs(directory)
+                except OSError as e:
+                    # Doesn't matter if it already exists.
+                    if e.errno != errno.EEXIST:
+                        raise
+            with open(path, 'w') as the_file:
+                the_file.write(content)
+
+
+class TestDetectIntltool(TestCase, SetupTestPackageMixin):
+
+    def test_detect_potfiles_in(self):
+        # Find POTFILES.in in a package with multiple dirs when only one has
+        # POTFILES.in.
+        self.prepare_package("intltool_POTFILES_in_1")
+        dirs = find_potfiles_in()
+        self.assertThat(dirs, MatchesSetwise(Equals("./po-intltool")))
+
+    def test_detect_potfiles_in_module(self):
+        # Find POTFILES.in in a package with POTFILES.in at different levels.
+        self.prepare_package("intltool_POTFILES_in_2")
+        dirs = find_potfiles_in()
+        self.assertThat(
+            dirs, MatchesSetwise(Equals("./po"), Equals("./module1/po")))
+
+    def test_check_potfiles_in_content_ok(self):
+        # Ideally all files listed in POTFILES.in exist in the source package.
+        self.prepare_package("intltool_single_ok")
+        self.assertTrue(check_potfiles_in("./po"))
+
+    def test_check_potfiles_in_content_ok_file_added(self):
+        # If a file is not listed in POTFILES.in, the file is still good for
+        # our purposes.
+        self.prepare_package("intltool_single_ok")
+        with open("./src/sourcefile_new.c", "w") as added_file:
+            added_file.write("/* Test file. */")
+        self.assertTrue(check_potfiles_in("./po"))
+
+    def test_check_potfiles_in_content_not_ok_file_removed(self):
+        # If a file is missing that is listed in POTFILES.in, the file
+        # intltool structure is probably broken and cannot be used for
+        # our purposes.
+        self.prepare_package("intltool_single_ok")
+        os.remove("./src/sourcefile1.c")
+        self.assertFalse(check_potfiles_in("./po"))
+
+    def test_check_potfiles_in_wrong_directory(self):
+        # Passing in the wrong directory will cause the check to fail
+        # gracefully and return False.
+        self.prepare_package("intltool_single_ok")
+        self.assertFalse(check_potfiles_in("./foo"))
+
+    def test_find_intltool_dirs(self):
+        # Complete run: find all directories with intltool structure.
+        self.prepare_package("intltool_full_ok")
+        self.assertEqual(
+            ["./po-module1", "./po-module2"], find_intltool_dirs())
+
+    def test_find_intltool_dirs_broken(self):
+        # Complete run: part of the intltool structure is broken.
+        self.prepare_package("intltool_full_ok")
+        os.remove("./src/module1/sourcefile1.c")
+        self.assertEqual(
+            ["./po-module2"], find_intltool_dirs())
+
+
+class TestIntltoolDomain(TestCase, SetupTestPackageMixin):
+
+    def test_get_translation_domain_makevars(self):
+        # Find a translation domain in Makevars.
+        self.prepare_package("intltool_domain_makevars")
+        self.assertEqual(
+            "translationdomain",
+            get_translation_domain("po"))
+
+    def test_get_translation_domain_makevars_subst_1(self):
+        # Find a translation domain in Makevars, substituted from
+        # Makefile.in.in.
+        self.prepare_package(
+            "intltool_domain_base",
+            {
+                "po/Makefile.in.in": "PACKAGE=packagename-in-in\n",
+                "po/Makevars": "DOMAIN = $(PACKAGE)\n",
+            })
+        self.assertEqual(
+            "packagename-in-in",
+            get_translation_domain("po"))
+
+    def test_get_translation_domain_makevars_subst_2(self):
+        # Find a translation domain in Makevars, substituted from
+        # configure.ac.
+        self.prepare_package(
+            "intltool_domain_base",
+            {
+                "configure.ac": "PACKAGE=packagename-ac\n",
+                "po/Makefile.in.in": "# No domain here.\n",
+                "po/Makevars": "DOMAIN = $(PACKAGE)\n",
+            })
+        self.assertEqual(
+            "packagename-ac",
+            get_translation_domain("po"))
+
+    def test_get_translation_domain_makefile_in_in(self):
+        # Find a translation domain in Makefile.in.in.
+        self.prepare_package("intltool_domain_makefile_in_in")
+        self.assertEqual(
+            "packagename-in-in",
+            get_translation_domain("po"))
+
+    def test_get_translation_domain_configure_ac(self):
+        # Find a translation domain in configure.ac.
+        self.prepare_package("intltool_domain_configure_ac")
+        self.assertEqual(
+            "packagename-ac",
+            get_translation_domain("po"))
+
+    def prepare_ac_init(self, parameters):
+        # Prepare test for various permutations of AC_INIT parameters
+        configure_ac_content = dedent("""
+            AC_INIT(%s)
+            GETTEXT_PACKAGE=AC_PACKAGE_NAME
+            """) % parameters
+        self.prepare_package(
+            "intltool_domain_base",
+            {
+                "configure.ac": configure_ac_content,
+            })
+
+    def test_get_translation_domain_configure_ac_init(self):
+        # Find a translation domain in configure.ac in AC_INIT.
+        self.prepare_ac_init("packagename-ac-init, 1.0, http://bug.org";)
+        self.assertEqual(
+            "packagename-ac-init",
+            get_translation_domain("po"))
+
+    def test_get_translation_domain_configure_ac_init_single_param(self):
+        # Find a translation domain in configure.ac in AC_INIT.
+        self.prepare_ac_init("[Just 1 param]")
+        self.assertIsNone(get_translation_domain("po"))
+
+    def test_get_translation_domain_configure_ac_init_brackets(self):
+        # Find a translation domain in configure.ac in AC_INIT with brackets.
+        self.prepare_ac_init("[packagename-ac-init], 1.0, http://bug.org";)
+        self.assertEqual(
+            "packagename-ac-init",
+            get_translation_domain("po"))
+
+    def test_get_translation_domain_configure_ac_init_tarname(self):
+        # Find a translation domain in configure.ac in AC_INIT tar name
+        # parameter.
+        self.prepare_ac_init(
+            "[Package name], 1.0, http://bug.org, [package-tarname]")
+        self.assertEqual(
+            "package-tarname",
+            get_translation_domain("po"))
+
+    def test_get_translation_domain_configure_ac_init_multiline(self):
+        # Find a translation domain in configure.ac in AC_INIT when it
+        # spans multiple lines.
+        self.prepare_ac_init(
+            "[packagename-ac-init],\n    1.0,\n    http://bug.org";)
+        self.assertEqual(
+            "packagename-ac-init",
+            get_translation_domain("po"))
+
+    def test_get_translation_domain_configure_ac_init_multiline_tarname(self):
+        # Find a translation domain in configure.ac in AC_INIT tar name
+        # parameter that is on a different line.
+        self.prepare_ac_init(
+            "[Package name], 1.0,\n    http://bug.org, [package-tarname]")
+        self.assertEqual(
+            "package-tarname",
+            get_translation_domain("po"))
+
+    def test_get_translation_domain_configure_in(self):
+        # Find a translation domain in configure.in.
+        self.prepare_package("intltool_domain_configure_in")
+        self.assertEqual(
+            "packagename-in",
+            get_translation_domain("po"))
+
+    def test_get_translation_domain_makefile_in_in_substitute(self):
+        # Find a translation domain in Makefile.in.in with substitution from
+        # configure.ac.
+        self.prepare_package("intltool_domain_makefile_in_in_substitute")
+        self.assertEqual(
+            "domainname-ac-in-in",
+            get_translation_domain("po"))
+
+    def test_get_translation_domain_makefile_in_in_substitute_same_name(self):
+        # Find a translation domain in Makefile.in.in with substitution from
+        # configure.ac from a variable with the same name as in
+        # Makefile.in.in.
+        self.prepare_package(
+            "intltool_domain_makefile_in_in_substitute_same_name")
+        self.assertEqual(
+            "packagename-ac-in-in",
+            get_translation_domain("po"))
+
+    def test_get_translation_domain_makefile_in_in_substitute_same_file(self):
+        # Find a translation domain in Makefile.in.in with substitution from
+        # the same file.
+        self.prepare_package(
+            "intltool_domain_makefile_in_in_substitute_same_file")
+        self.assertEqual(
+            "domain-in-in-in-in",
+            get_translation_domain("po"))
+
+    def test_get_translation_domain_makefile_in_in_substitute_broken(self):
+        # Find no translation domain in Makefile.in.in when the substitution
+        # cannot be fulfilled.
+        self.prepare_package(
+            "intltool_domain_makefile_in_in_substitute_broken")
+        self.assertIsNone(get_translation_domain("po"))
+
+    def test_get_translation_domain_configure_in_substitute_version(self):
+        # Find a translation domain in configure.in with Makefile-style
+        # substitution from the same file.
+        self.prepare_package(
+            "intltool_domain_configure_in_substitute_version")
+        self.assertEqual(
+            "domainname-in42",
+            get_translation_domain("po"))
+
+
+class TestGenerateTemplates(TestCase, SetupTestPackageMixin):
+
+    def test_generate_pot(self):
+        # Generate a given PO template.
+        self.prepare_package("intltool_full_ok")
+        self.assertTrue(
+            generate_pot("./po-module1", "module1"),
+            "PO template generation failed.")
+        expected_path = "./po-module1/module1.pot"
+        self.assertTrue(
+            os.access(expected_path, os.F_OK),
+            "Generated PO template '%s' not found." % expected_path)
+
+    def test_generate_pot_no_domain(self):
+        # Generate a generic PO template.
+        self.prepare_package("intltool_full_ok")
+        self.assertTrue(
+            generate_pot("./po-module1", None),
+            "PO template generation failed.")
+        expected_path = "./po-module1/messages.pot"
+        self.assertTrue(
+            os.access(expected_path, os.F_OK),
+            "Generated PO template '%s' not found." % expected_path)
+
+    def test_generate_pot_empty_domain(self):
+        # Generate a generic PO template.
+        self.prepare_package("intltool_full_ok")
+        self.assertTrue(
+            generate_pot("./po-module1", ""),
+            "PO template generation failed.")
+        expected_path = "./po-module1/messages.pot"
+        self.assertTrue(
+            os.access(expected_path, os.F_OK),
+            "Generated PO template '%s' not found." % expected_path)
+
+    def test_generate_pot_not_intltool(self):
+        # Fail when not an intltool setup.
+        self.prepare_package("intltool_full_ok")
+        # Cripple the setup.
+        os.remove("./po-module1/POTFILES.in")
+        self.assertFalse(
+            generate_pot("./po-module1", "nothing"),
+            "PO template generation should have failed.")
+        not_expected_path = "./po-module1/nothing.pot"
+        self.assertFalse(
+            os.access(not_expected_path, os.F_OK),
+            "Not expected PO template '%s' generated." % not_expected_path)
+
+    def test_generate_pots(self):
+        # Generate all PO templates in the package.
+        self.prepare_package("intltool_full_ok")
+        expected_paths = [
+            './po-module1/packagename-module1.pot',
+            './po-module2/packagename-module2.pot',
+            ]
+        pots_list = generate_pots()
+        self.assertEqual(expected_paths, pots_list)
+        for expected_path in expected_paths:
+            self.assertTrue(
+                os.access(expected_path, os.F_OK),
+                "Generated PO template '%s' not found." % expected_path)
+
+
+class TestConfigFile(TestCase):
+
+    def _makeConfigFile(self, text):
+        """Create a `ConfigFile` containing `text`."""
+        return ConfigFile(StringIO(dedent(text)))
+
+    def test_getVariable_smoke(self):
+        configfile = self._makeConfigFile("""
+            A = 1
+            B = 2
+            C = 3
+            """)
+        self.assertEqual('1', configfile.getVariable('A'))
+        self.assertEqual('2', configfile.getVariable('B'))
+        self.assertEqual('3', configfile.getVariable('C'))
+
+    def test_getVariable_exists(self):
+        configfile = self._makeConfigFile("DDD=dd.d")
+        self.assertEqual('dd.d', configfile.getVariable('DDD'))
+
+    def test_getVariable_ignores_mere_mention(self):
+        configfile = self._makeConfigFile("""
+            CCC
+            CCC = ccc # (this is the real definition)
+            CCC
+            """)
+        self.assertEqual('ccc', configfile.getVariable('CCC'))
+
+    def test_getVariable_ignores_irrelevancies(self):
+        configfile = self._makeConfigFile("""
+            A = a
+            ===
+            blah
+            FOO(n, m)
+            a = case-insensitive
+
+            Z = z
+            """)
+        self.assertEqual('a', configfile.getVariable('A'))
+        self.assertEqual('z', configfile.getVariable('Z'))
+
+    def test_getVariable_exists_spaces_comment(self):
+        configfile = self._makeConfigFile("CCC = ccc # comment")
+        self.assertEqual('ccc', configfile.getVariable('CCC'))
+
+    def test_getVariable_empty(self):
+        configfile = self._makeConfigFile("AAA=")
+        self.assertEqual('', configfile.getVariable('AAA'))
+
+    def test_getVariable_empty_spaces(self):
+        configfile = self._makeConfigFile("BBB = ")
+        self.assertEqual('', configfile.getVariable('BBB'))
+
+    def test_getVariable_nonexistent(self):
+        configfile = self._makeConfigFile("X = y")
+        self.assertIsNone(configfile.getVariable('FFF'))
+
+    def test_getVariable_broken(self):
+        configfile = self._makeConfigFile("EEE \n= eee")
+        self.assertIsNone(configfile.getVariable('EEE'))
+
+    def test_getVariable_strips_quotes(self):
+        # Quotes get stripped off variables.
+        configfile = self._makeConfigFile("QQQ = 'qqq'")
+        self.assertEqual('qqq', configfile.getVariable('QQQ'))
+
+        # This is done by invoking _stripQuotes (tested separately).
+        configfile._stripQuotes = FakeMethod(result='foo')
+        self.assertEqual('foo', configfile.getVariable('QQQ'))
+        self.assertNotEqual(0, configfile._stripQuotes.call_count)
+
+    def test_getFunctionParams_single(self):
+        configfile = self._makeConfigFile("FUNC_1(param1)")
+        self.assertEqual(['param1'], configfile.getFunctionParams('FUNC_1'))
+
+    def test_getFunctionParams_multiple(self):
+        configfile = self._makeConfigFile("FUNC_2(param1, param2, param3 )")
+        self.assertEqual(
+            ['param1', 'param2', 'param3'],
+            configfile.getFunctionParams('FUNC_2'))
+
+    def test_getFunctionParams_multiline_indented(self):
+        configfile = self._makeConfigFile("""
+            ML_FUNC_1(param1,
+                param2, param3)
+            """)
+        self.assertEqual(
+            ['param1', 'param2', 'param3'],
+            configfile.getFunctionParams('ML_FUNC_1'))
+
+    def test_getFunctionParams_multiline_not_indented(self):
+        configfile = self._makeConfigFile("""
+            ML_FUNC_2(
+            param1,
+            param2)
+            """)
+        self.assertEqual(
+            ['param1', 'param2'], configfile.getFunctionParams('ML_FUNC_2'))
+
+    def test_getFunctionParams_strips_quotes(self):
+        # Quotes get stripped off function parameters.
+        configfile = self._makeConfigFile('FUNC("param")')
+        self.assertEqual(['param'], configfile.getFunctionParams('FUNC'))
+
+        # This is done by invoking _stripQuotes (tested separately).
+        configfile._stripQuotes = FakeMethod(result='arg')
+        self.assertEqual(['arg'], configfile.getFunctionParams('FUNC'))
+        self.assertNotEqual(0, configfile._stripQuotes.call_count)
+
+    def test_stripQuotes_unquoted(self):
+        # _stripQuotes leaves unquoted identifiers intact.
+        configfile = self._makeConfigFile('')
+        self.assertEqual('hello', configfile._stripQuotes('hello'))
+
+    def test_stripQuotes_empty(self):
+        configfile = self._makeConfigFile('')
+        self.assertEqual('', configfile._stripQuotes(''))
+
+    def test_stripQuotes_single_quotes(self):
+        # Single quotes are stripped.
+        configfile = self._makeConfigFile('')
+        self.assertEqual('x', configfile._stripQuotes("'x'"))
+
+    def test_stripQuotes_double_quotes(self):
+        # Double quotes are stripped.
+        configfile = self._makeConfigFile('')
+        self.assertEqual('y', configfile._stripQuotes('"y"'))
+
+    def test_stripQuotes_bracket_quotes(self):
+        # Brackets are stripped.
+        configfile = self._makeConfigFile('')
+        self.assertEqual('z', configfile._stripQuotes('[z]'))
+
+    def test_stripQuotes_opening_brackets(self):
+        # An opening bracket must be matched by a closing one.
+        configfile = self._makeConfigFile('')
+        self.assertEqual('[x[', configfile._stripQuotes('[x['))
+
+    def test_stripQuotes_closing_brackets(self):
+        # A closing bracket is not accepted as an opening quote.
+        configfile = self._makeConfigFile('')
+        self.assertEqual(']x]', configfile._stripQuotes(']x]'))
+
+    def test_stripQuotes_multiple(self):
+        # Only a single layer of quotes is stripped.
+        configfile = self._makeConfigFile('')
+        self.assertEqual('"n"', configfile._stripQuotes("'\"n\"'"))
+
+    def test_stripQuotes_single_quote(self):
+        # A string consisting of just one quote is not stripped.
+        configfile = self._makeConfigFile('')
+        self.assertEqual("'", configfile._stripQuotes("'"))
+
+    def test_stripQuotes_mismatched(self):
+        # Mismatched quotes are not stripped.
+        configfile = self._makeConfigFile('')
+        self.assertEqual("'foo\"", configfile._stripQuotes("'foo\""))
+
+    def test_stripQuotes_unilateral(self):
+        # A quote that's only on one end doesn't get stripped.
+        configfile = self._makeConfigFile('')
+        self.assertEqual('"foo', configfile._stripQuotes('"foo'))