summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/conf.py4
-rw-r--r--doc/development/testing.txt31
-rw-r--r--doc/development/unit-testing.txt354
-rw-r--r--testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestDevice.py4
-rw-r--r--testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestDirectory.py4
-rw-r--r--testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestFile.py4
-rw-r--r--testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestHardlink.py4
-rw-r--r--testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestNonexistent.py4
-rw-r--r--testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestSymlink.py4
-rw-r--r--testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/Test__init.py4
-rw-r--r--testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/Testbase.py7
-rw-r--r--testsuite/Testsrc/Testlib/TestEncryption.py3
-rw-r--r--testsuite/Testsrc/Testlib/TestOptions.py4
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testbase.py3
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testexceptions.py3
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testhelpers.py7
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testinterfaces.py3
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgCheetahGenerator.py4
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgEncryptedCheetahGenerator.py2
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgEncryptedGenerator.py4
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgEncryptedGenshiGenerator.py4
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgExternalCommandVerifier.py4
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgGenshiGenerator.py4
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgInfoXML.py4
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/Test_init.py4
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestMetadata.py6
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProbes.py4
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProperties.py4
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestSEModules.py4
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestTemplateHelper.py3
-rw-r--r--testsuite/Testsrc/test_code_checks.py3
-rw-r--r--testsuite/Testtools/__init__.py4
-rw-r--r--testsuite/common.py145
33 files changed, 532 insertions, 118 deletions
diff --git a/doc/conf.py b/doc/conf.py
index 538cd236d..4dda8327f 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -20,6 +20,7 @@ import time
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath('../src/lib'))
+sys.path.insert(0, os.path.abspath('..'))
# -- General configuration -----------------------------------------------------
@@ -263,7 +264,8 @@ def setup(app):
versions = ["3.2", "2.7", "2.6"]
cur_version = '.'.join(str(v) for v in sys.version_info[0:2])
-intersphinx_mapping = dict()
+intersphinx_mapping = dict(mock=('http://www.voidspace.org.uk/python/mock',
+ None))
for pyver in versions:
if pyver == cur_version:
key = 'py'
diff --git a/doc/development/testing.txt b/doc/development/testing.txt
index 1f0842053..f00193574 100644
--- a/doc/development/testing.txt
+++ b/doc/development/testing.txt
@@ -8,22 +8,24 @@ Testing
Testing Prereleases
-------------------
-Before each release, several prereleases will be tagged. It is
-helpful to have users test these releases (when feasible) because
-it is hard to replicate the full range of potential reconfiguration
-situations; between different operating systems, system management
-tools, and configuration specification variation, there can be
-large differences between sites.
+Before each release, several prereleases will be tagged. It is helpful
+to have users test these releases (when feasible) because it is hard
+to replicate the full range of potential reconfiguration situations;
+between different operating systems, system management tools, and
+configuration specification variation, there can be large differences
+between sites.
-For more details please visit `Tracking Development Releases of Bcfg2 <http://trac.mcs.anl.gov/projects/bcfg2/wiki/TrackingDevelopmentTrunk>`_ .
+For more details please visit `Tracking Development Releases of Bcfg2
+<http://trac.mcs.anl.gov/projects/bcfg2/wiki/TrackingDevelopmentTrunk>`_
+.
Upgrade Testing
---------------
-This section describes upgrade procedures to completely test the
-client and server. These procedures can be used for either pre-release
+This section describes upgrade procedures to completely test the
+client and server. These procedures can be used for either pre-release
testing, or for confidence building in a new release.
@@ -33,9 +35,9 @@ Server Testing
1. Ensure that the server produces the same configurations for clients
* Before the upgrade, generate all client configurations using the
- buildall subcommand of bcfg2-info. This subcommand takes a directory
- argument; it will generate one client configuration in each file,
- naming each according to the client name.
+ buildall subcommand of bcfg2-info. This subcommand takes a
+ directory argument; it will generate one client configuration in
+ each file, naming each according to the client name.
.. code-block:: sh
@@ -54,7 +56,8 @@ Server Testing
* Upgrade the server software
* Generate all client configurations in a second location using the
new software. Any tracebacks reflect bugs, and should be filed in
- the ticketing system. Any new messages should be carefully examined.
+ the ticketing system. Any new messages should be carefully
+ examined.
* Compare each file in the old directory to those in the new directory
using ``bcfg2-admin compare -r /old/directory /new/directory``
@@ -78,5 +81,5 @@ Server Testing
Client Testing
^^^^^^^^^^^^^^
-Run the client in dry-run and non-dry-run mode; ensure that multiple
+Run the client in dry-run and non-dry-run mode; ensure that multiple
runs produce consistent results.
diff --git a/doc/development/unit-testing.txt b/doc/development/unit-testing.txt
index 7af969686..a4ec2ad5c 100644
--- a/doc/development/unit-testing.txt
+++ b/doc/development/unit-testing.txt
@@ -6,12 +6,14 @@
Bcfg2 unit testing
==================
-.. _Python Mock Module: http://python-mock.sourceforge.net/
+.. _Python Mock Module: http://www.voidspace.org.uk/python/mock
.. _Python Nose: http://readthedocs.org/docs/nose/en/latest/
You will first need to install the `Python Mock Module`_ and `Python
Nose`_ modules. You can then run the existing tests with the
-following.::
+following:
+
+.. code-block: bash
cd testsuite
nosetests
@@ -26,3 +28,351 @@ You should see output something like the following::
Unit tests are also run by Travis-CI, a free continuous integration
service, at http://travis-ci.org/#!/Bcfg2/bcfg2/
+
+Testing in a virtualenv
+=======================
+
+Travis-CI runs the unit tests in a virtual environment, so to emulate
+that testing environment as closely as possible you can also use a
+virtual environment. To do so, you must have `virtualenv
+<http://www.virtualenv.org/>`_ installed.
+
+There are two ways to test: Either with just the bare essential
+packages installed, or with optional packages installed as well.
+(Optional packages are things like Genshi; you can run Bcfg2 with them
+or without them.) For completeness, the tests should be run in both
+manners. (On Python 3, almost none of the optional packages are
+available, so it can only be run with just the required packages.) To
+install the optional packages, set:
+
+.. code-block:: bash
+
+ export WITH_OPTIONAL_DEPS=yes
+
+This flag tells the install script to install optional dependencies as
+well as requirements.
+
+This assumes that you will create a virtual environment in
+``~/venvs/``, and that the Bcfg2 source tree is cloned into
+``~/bcfg2``.
+
+First, create a new virtual environment and activate it:
+
+.. code-block:: bash
+
+ cd ~/venvs
+ virtualenv travis
+ source travis/bin/activate
+
+Get the test suite from bcfg2:
+
+.. code-block:: bash
+
+ cp -R ~/bcfg2/* ~/venvs/travis/
+
+Next, you must install prerequisite packages that are required to
+build some of the required Python packages, and some optional packages
+that are much easier to install from binary (rather than from source).
+If you are running on Ubuntu (the platform Travis-CI runs on) and have
+sudo, you can simply run:
+
+.. code-block:: bash
+
+ testsuite/before_install.sh
+
+If not, you will need to examine ``testsuite/before_install.sh``
+and install the packages manually. The equivalent for Fedora, for
+instance, would be:
+
+.. code-block:: bash
+
+ yum -y update
+ yum -y install swig pylint
+ if [[ "$WITH_OPTIONAL_DEPS" == "yes" ]]; then
+ sudo yum -y install libselinux-python pylibacl python-inotify \
+ PyYAML
+ fi
+
+Next, install required Python packages:
+
+.. code-block:: bash
+
+ testsuite/install.sh
+
+Install Bcfg2 itself to the virtualenv:
+
+.. code-block:: bash
+
+ pip install -e .
+
+Now you can run tests:
+
+.. code-block:: bash
+
+ nosetests testsuite
+
+Writing Unit Tests
+==================
+
+Bcfg2 makes extremely heavy use of object inheritance, which can make
+it challenging at times to write reusable tests. For instance, when
+writing tests for the base :class:`Bcfg2.Server.Plugin.base.Plugin`
+class, which all Bcfg2 :ref:`server-plugins-index` inherit from via
+the :mod:`Plugin interfaces <Bcfg2.Server.Plugin.interfaces>`,
+yielding several levels of often-multiple inheritance. To make this
+easier, our unit tests adhere to several design considerations:
+
+Inherit Tests
+-------------
+
+Our test objects should have inheritance trees that mirror the
+inheritance trees of their tested objects. For instance, the
+:class:`Bcfg2.Server.Plugins.Metadata.Metadata` class definition is:
+
+.. code-block:: python
+
+ class Metadata(Bcfg2.Server.Plugin.Metadata,
+ Bcfg2.Server.Plugin.Statistics,
+ Bcfg2.Server.Plugin.DatabaseBacked):
+
+Consequently, the ``TestMetadata`` class definition is:
+
+.. code-block:: python
+
+ class TestMetadata(TestPlugin.TestMetadata,
+ TestPlugin.TestStatistics,
+ TestPlugin.TestDatabaseBacked):
+
+.. note::
+
+ The test object names are abbreviated because of the system of
+ relative imports in the ``testsuite`` tree, described below.
+
+This gives us a large number of tests basically "for free": all core
+:class:`Bcfg2.Server.Plugin.interfaces.Metadata`,
+:class:`Bcfg2.Server.Plugin.interfaces.Statistics`, and
+:class:`Bcfg2.Server.Plugin.helpers.DatabaseBacked` functionality is
+automatically tested on the ``Metadata`` class, which gives the test
+writer a lot of free functionality and also an easy list of which
+tests must be overridden to provide tests appropriate for the ``Metadata``
+class implementation.
+
+Additionally, a test class should have a class variable that describes
+the class that is being tested, and tests in that class should use
+that class variable to instantate the tested object. For instance,
+the test for :class:`Bcfg2.Server.Plugin.helpers.DirectoryBacked`
+looks like this:
+
+.. code-block:: python
+
+ class TestDirectoryBacked(Bcfg2TestCase):
+ test_obj = DirectoryBacked
+ ...
+
+
+ def test_child_interface(self):
+ """ ensure that the child object has the correct interface """
+ self.assertTrue(hasattr(self.test_obj.__child__, "HandleEvent"))
+
+Then test objects that inherit from ``TestDirectoryBacked`` can
+override that object, and the ``test_child_interface`` test (e.g.)
+will still work. For example:
+
+.. code-block:: python
+
+ class TestPropDirectoryBacked(TestDirectoryBacked):
+ test_obj = PropDirectoryBacked
+
+Finally, each test class must also provide a ``get_obj`` method that
+takes no required arguments and produces an instance of ``test_obj``.
+All test methods must use ``self.get_obj()`` to instantiate an object
+to be tested.
+
+An object that does not inherit from any other tested Bcfg2 objects
+should inherit from :class:`testsuite.common.Bcfg2TestCase`, described
+below.
+
+.. _development-unit-testing-relative-imports:
+
+Relative Imports
+----------------
+
+In order to reuse test code and allow for test inheritance, each test
+module should add all parent module paths to its ``sys.path``. For
+instance, assuming a test in
+``testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestMetadata.py``,
+the following paths should be added to ``sys.path``::
+
+ testsuite
+ testsuite/Testsrc
+ testsuite/Testsrc/Testlib
+ testsuite/Testsrc/Testlib/TestServer
+ testsuite/Testsrc/Testlib/TestServer/TestPlugins
+
+This must be done because Python 2.4, one of our target platforms,
+does not support relative imports. An easy way to do this is to add
+the following snippet to the top of each test file:
+
+.. code-block:: python
+
+ import os
+ import sys
+
+ # add all parent testsuite directories to sys.path to allow (most)
+ # relative imports in python 2.4
+ path = os.path.dirname(__file__)
+ while path != "/":
+ if os.path.basename(path).lower().startswith("test"):
+ sys.path.append(path)
+ if os.path.basename(path) == "testsuite":
+ break
+ path = os.path.dirname(path)
+
+In addition, each new directory created in ``testsuite`` must contain
+an empty ``__init__.py``.
+
+This will allow you, within ``TestMetadata.py``, to import common test
+code and the parent objects the ``TestMetadata`` class will inherit from:
+
+.. code-block:: python
+
+ from common import inPy3k, call, builtins, u, can_skip, \
+ skip, skipIf, skipUnless, Bcfg2TestCase, DBModelTestCase, syncdb, \
+ patchIf, datastore
+ from TestPlugin import TestXMLFileBacked, TestMetadata as _TestMetadata, \
+ TestStatistics, TestDatabaseBacked
+
+Avoid Patching Where Possible
+-----------------------------
+
+The `Python Mock Module`_ provides a ``patch`` decorator that can be
+used to replace tested objects with ``Mock`` objects. This is
+wonderful and necessary, but due to differences in the way various
+versions of Python and Python Mock handle object scope, it's not
+always reliable when combined with our system of test object
+inheritance. Consequently, you should follow these rules when
+considering whether to use ``patch``:
+
+* If you need to mock an object that is not part of Bcfg2 (e.g., a
+ builtin or an object in another Python library), use ``patch``.
+* If you need to patch an object being tested in order to instantiate
+ it, use ``patch``, but see below.
+* If you need to patch a function (not a method) that is part of
+ Bcfg2, use ``patch``.
+* If you need to mock an object that is part of the object being
+ tested, do not use ``patch``.
+
+As an example of the last rule, assume you are writing tests for
+:class:`Bcfg2.Server.Plugin.helpers.FileBacked`.
+:func:`Bcfg2.Server.Plugin.helpers.FileBacked.HandleEvent` calls
+:func:`Bcfg2.Server.Plugin.helpers.FileBacked.Index`, so we need to
+mock the ``Index`` function. This is the **wrong** way to do that:
+
+.. code-block:: python
+
+ class TestFileBacked(Bcfg2TestCase):
+ @patch("%s.open" % builtins)
+ @patch("Bcfg2.Server.Plugin.helpers.FileBacked.Index")
+ def test_HandleEvent(self, mock_Index, mock_open):
+ ...
+
+Tests that inherit from ``TestFileBacked`` will not reliably patch the
+correct ``Index`` function. Instead, assign the object to be mocked
+directly:
+
+.. code-block:: python
+
+ class TestFileBacked(Bcfg2TestCase):
+ @patch("%s.open" % builtins)
+ def test_HandleEvent(self, mock_open):
+ fb = self.get_obj()
+ fb.Index = Mock()
+
+.. note::
+
+ ``@patch`` decorations are evaluated at compile-time, so a
+ workaround like this does **not** work:
+
+ .. code-block:: python
+
+ class TestFileBacked(Bcfg2TestCase):
+ @patch("%s.open" % builtins)
+ @patch("%s.%s.Index" % (self.test_obj.__module__,
+ self.test_obj.__name))
+ def test_HandleEvent(self, mock_Index, mock_open):
+ ...
+
+ But see below about patching objects before instantiation.
+
+In some cases, you will need to patch an object in order to
+instantiate it. For instance, consider
+:class:`Bcfg2.Server.Plugin.helpers.DirectoryBacked`, which attempts
+to set a file access monitor watch when it is instantiated. This
+won't work during unit testing, so we have to patch
+:func:`Bcfg2.Server.Plugin.helpers.DirectoryBacked.add_directory_monitor`
+in order to successfully instantiate a ``DirectoryBacked`` object. In
+order to do that, we need to patch the object being tested, which is a
+variable, but we need to evaluate the patch at run-time, not at
+compile time, in order to deal with inheritance. This can be done
+with a ``@patch`` decorator on an inner function, e.g.:
+
+.. code-block:: python
+
+ class TestDirectoryBacked(Bcfg2TestCase):
+ test_obj = DirectoryBacked
+
+ def test__init(self):
+ @patch("%s.%s.add_directory_monitor" % (self.test_obj.__module__,
+ self.test_obj.__name__))
+ def inner(mock_add_monitor):
+ db = self.test_obj(datastore, Mock())
+ mock_add_monitor.assert_called_with('')
+
+ inner()
+
+``inner()`` is patched when ``test__init()`` is called, and so
+``@patch()`` is called with the module and the name of the object
+being tested as defined by the test object (i.e., not as defined by
+the parent object). If this is not done, then the patch will be
+applied at compile-time and ``add_directory_monitor`` will be patched
+on the ``DirectoryBacked`` class instead of on the class to be tested.
+
+Some of our older unit tests do not follow these rules religiously, so
+as more tests are written that inherit from larger portions of the
+``testsuite`` tree they may need to be refactored.
+
+Naming
+------
+
+In order to make the system of inheritance we implement possible, we
+must follow these naming conventions fairly religiously.
+
+* Test classes are given the name of the object to be tested with
+ ``Test`` prepended. E.g., the test for the
+ :class:`Bcfg2.Server.Plugins.Metadata.Metadata` is named
+ ``TestMetadata``.
+* Test classes that test miscellaneous functions in a module are named
+ ``TestFunctions``.
+* Test modules are given the name of the module to be tested with
+ ``Test`` prepended. Tests for ``__init__.py`` are named
+ ``Test_init.py`` (one underscore).
+* Tests for methods or functions are given the name of the method or
+ function to be tested with ``test_`` prepended. E.g., the test for
+ :class:`Bcfg2.Server.Plugin.helpers.StructFile.Match` is called
+ ``test_Match``; the test for
+ :class:`Bcfg2.Server.Plugin.helpers.StructFile._match` is called
+ ``test__match``.
+* Tests for magic methods -- those that start and end with double
+ underscores -- are named ``test__<name>``, where name is the name of
+ the magic method without underscores. E.g., a test for ``__init__``
+ is called ``test__init``, and a test for ``__getitem__`` is called
+ ``test__getitem``. If this causes a collision with a non-magic
+ function (e.g., if a class also has a function called
+ ``_getitem()``, the test for which would also be called
+ ``test__getitem``, seriously consider refactoring the code for the
+ class.
+
+Common Test Code
+----------------
+
+.. automodule:: testsuite.common
diff --git a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestDevice.py b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestDevice.py
index a18327fe0..9f396b0b6 100644
--- a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestDevice.py
+++ b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestDevice.py
@@ -16,9 +16,7 @@ while path != "/":
path = os.path.dirname(path)
from Test__init import get_posix_object
from Testbase import TestPOSIXTool
-from common import XI_NAMESPACE, XI, inPy3k, call, builtins, u, can_skip, \
- skip, skipIf, skipUnless, Bcfg2TestCase, DBModelTestCase, syncdb, \
- patchIf, datastore
+from common import *
class TestPOSIXDevice(TestPOSIXTool):
test_obj = POSIXDevice
diff --git a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestDirectory.py b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestDirectory.py
index e01bd7453..1ce6562c5 100644
--- a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestDirectory.py
+++ b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestDirectory.py
@@ -17,9 +17,7 @@ while path != "/":
path = os.path.dirname(path)
from Test__init import get_posix_object
from Testbase import TestPOSIXTool
-from common import XI_NAMESPACE, XI, inPy3k, call, builtins, u, can_skip, \
- skip, skipIf, skipUnless, Bcfg2TestCase, DBModelTestCase, syncdb, \
- patchIf, datastore
+from common import *
class TestPOSIXDirectory(TestPOSIXTool):
test_obj = POSIXDirectory
diff --git a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestFile.py b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestFile.py
index cdf11ce5e..f69c33a97 100644
--- a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestFile.py
+++ b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestFile.py
@@ -20,9 +20,7 @@ while path != "/":
path = os.path.dirname(path)
from Test__init import get_posix_object
from Testbase import TestPOSIXTool
-from common import XI_NAMESPACE, XI, inPy3k, call, builtins, u, can_skip, \
- skip, skipIf, skipUnless, Bcfg2TestCase, DBModelTestCase, syncdb, \
- patchIf, datastore
+from common import *
def get_file_object(posix=None):
if posix is None:
diff --git a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestHardlink.py b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestHardlink.py
index d68e15837..c38e86aeb 100644
--- a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestHardlink.py
+++ b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestHardlink.py
@@ -16,9 +16,7 @@ while path != "/":
path = os.path.dirname(path)
from Test__init import get_posix_object
from Testbase import TestPOSIXTool
-from common import XI_NAMESPACE, XI, inPy3k, call, builtins, u, can_skip, \
- skip, skipIf, skipUnless, Bcfg2TestCase, DBModelTestCase, syncdb, \
- patchIf, datastore
+from common import *
class TestPOSIXHardlink(TestPOSIXTool):
test_obj = POSIXHardlink
diff --git a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestNonexistent.py b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestNonexistent.py
index 375ff00eb..676b18f5d 100644
--- a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestNonexistent.py
+++ b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestNonexistent.py
@@ -16,9 +16,7 @@ while path != "/":
path = os.path.dirname(path)
from Test__init import get_config, get_posix_object
from Testbase import TestPOSIXTool
-from common import XI_NAMESPACE, XI, inPy3k, call, builtins, u, can_skip, \
- skip, skipIf, skipUnless, Bcfg2TestCase, DBModelTestCase, syncdb, \
- patchIf, datastore
+from common import *
class TestPOSIXNonexistent(TestPOSIXTool):
test_obj = POSIXNonexistent
diff --git a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestSymlink.py b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestSymlink.py
index b02f7b3c3..4c8ddfa3f 100644
--- a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestSymlink.py
+++ b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestSymlink.py
@@ -16,9 +16,7 @@ while path != "/":
path = os.path.dirname(path)
from Test__init import get_posix_object
from Testbase import TestPOSIXTool
-from common import XI_NAMESPACE, XI, inPy3k, call, builtins, u, can_skip, \
- skip, skipIf, skipUnless, Bcfg2TestCase, DBModelTestCase, syncdb, \
- patchIf, datastore
+from common import *
class TestPOSIXSymlink(TestPOSIXTool):
test_obj = POSIXSymlink
diff --git a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/Test__init.py b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/Test__init.py
index 14a2520df..e503ebd38 100644
--- a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/Test__init.py
+++ b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/Test__init.py
@@ -14,9 +14,7 @@ while path != "/":
if os.path.basename(path) == "testsuite":
break
path = os.path.dirname(path)
-from common import XI_NAMESPACE, XI, inPy3k, call, builtins, u, can_skip, \
- skip, skipIf, skipUnless, Bcfg2TestCase, DBModelTestCase, syncdb, \
- patchIf, datastore
+from common import *
def get_config(entries):
config = lxml.etree.Element("Configuration")
diff --git a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/Testbase.py b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/Testbase.py
index bcb1d16af..39c0b02d0 100644
--- a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/Testbase.py
+++ b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/Testbase.py
@@ -17,9 +17,7 @@ while path != "/":
break
path = os.path.dirname(path)
from Test__init import get_posix_object
-from common import XI_NAMESPACE, XI, inPy3k, call, builtins, u, can_skip, \
- skip, skipIf, skipUnless, Bcfg2TestCase, DBModelTestCase, syncdb, \
- patchIf, datastore
+from common import *
try:
import selinux
@@ -33,6 +31,7 @@ try:
except ImportError:
HAS_ACLS = False
+
class TestPOSIXTool(Bcfg2TestCase):
test_obj = POSIXTool
@@ -52,7 +51,7 @@ class TestPOSIXTool(Bcfg2TestCase):
# fully_specified should do no checking on the abstract
# POSIXTool object
self.assertTrue(self.ptool.fully_specified(Mock()))
-
+
@patch('os.stat')
@patch('os.walk')
@patch("Bcfg2.Client.Tools.POSIX.base.%s._verify_metadata" %
diff --git a/testsuite/Testsrc/Testlib/TestEncryption.py b/testsuite/Testsrc/Testlib/TestEncryption.py
index 530592cd0..7e9d910d8 100644
--- a/testsuite/Testsrc/Testlib/TestEncryption.py
+++ b/testsuite/Testsrc/Testlib/TestEncryption.py
@@ -13,8 +13,7 @@ while path != "/":
if os.path.basename(path) == "testsuite":
break
path = os.path.dirname(path)
-from common import call, u, can_skip, skip, skipIf, skipUnless, \
- Bcfg2TestCase, patchIf
+from common import *
try:
from Bcfg2.Encryption import *
diff --git a/testsuite/Testsrc/Testlib/TestOptions.py b/testsuite/Testsrc/Testlib/TestOptions.py
index e20a320b1..dc91a499b 100644
--- a/testsuite/Testsrc/Testlib/TestOptions.py
+++ b/testsuite/Testsrc/Testlib/TestOptions.py
@@ -13,9 +13,7 @@ while path != '/':
if os.path.basename(path) == "testsuite":
break
path = os.path.dirname(path)
-from common import XI_NAMESPACE, XI, inPy3k, call, builtins, u, can_skip, \
- skip, skipIf, skipUnless, Bcfg2TestCase, DBModelTestCase, syncdb, \
- patchIf, datastore
+from common import *
class TestDefaultConfigParser(Bcfg2TestCase):
@patch("%s.ConfigParser.get" % ConfigParser.__name__)
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testbase.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testbase.py
index 4b2f1981f..7ee3697bb 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testbase.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testbase.py
@@ -13,8 +13,7 @@ while path != '/':
if os.path.basename(path) == "testsuite":
break
path = os.path.dirname(path)
-from common import call, builtins, skip, skipIf, skipUnless, Bcfg2TestCase, \
- patchIf, datastore
+from common import *
class TestDebuggable(Bcfg2TestCase):
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testexceptions.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testexceptions.py
index d2b72251e..916ce822d 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testexceptions.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testexceptions.py
@@ -12,8 +12,7 @@ while path != '/':
if os.path.basename(path) == "testsuite":
break
path = os.path.dirname(path)
-from common import call, builtins, skip, skipIf, skipUnless, Bcfg2TestCase, \
- patchIf, datastore
+from common import *
class TestPluginInitError(Bcfg2TestCase):
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testhelpers.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testhelpers.py
index 27d726fbb..8a1d5a949 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testhelpers.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testhelpers.py
@@ -17,11 +17,11 @@ while path != '/':
if os.path.basename(path) == "testsuite":
break
path = os.path.dirname(path)
-from common import XI_NAMESPACE, XI, call, builtins, skip, skipIf, \
- skipUnless, Bcfg2TestCase, patchIf, datastore, inPy3k, can_skip, re_type
+from common import *
from TestServer.TestPlugin.Testbase import TestPlugin, TestDebuggable
from TestServer.TestPlugin.Testinterfaces import TestGenerator
+
def tostring(el):
return lxml.etree.tostring(el, xml_declaration=False).decode('UTF-8')
@@ -155,12 +155,13 @@ class TestDirectoryBacked(Bcfg2TestCase):
# such thing as a bad event
def test_child_interface(self):
- # ensure that the child object has the correct interface
+ """ ensure that the child object has the correct interface """
self.assertTrue(hasattr(self.test_obj.__child__, "HandleEvent"))
def get_obj(self, fam=None):
if fam is None:
fam = Mock()
+
@patch("%s.%s.add_directory_monitor" % (self.test_obj.__module__,
self.test_obj.__name__),
Mock())
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testinterfaces.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testinterfaces.py
index 6ef40d385..ace509057 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testinterfaces.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testinterfaces.py
@@ -14,8 +14,7 @@ while path != '/':
if os.path.basename(path) == "testsuite":
break
path = os.path.dirname(path)
-from common import call, builtins, skip, skipIf, skipUnless, Bcfg2TestCase, \
- patchIf, datastore
+from common import *
from TestServer.TestPlugin.Testbase import TestPlugin
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgCheetahGenerator.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgCheetahGenerator.py
index 2577b29df..1832e5e03 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgCheetahGenerator.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgCheetahGenerator.py
@@ -13,9 +13,7 @@ while path != "/":
if os.path.basename(path) == "testsuite":
break
path = os.path.dirname(path)
-from common import XI_NAMESPACE, XI, inPy3k, call, builtins, u, can_skip, \
- skip, skipIf, skipUnless, Bcfg2TestCase, DBModelTestCase, syncdb, \
- patchIf, datastore, re_type
+from common import *
from TestServer.TestPlugins.TestCfg.Test_init import TestCfgGenerator
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgEncryptedCheetahGenerator.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgEncryptedCheetahGenerator.py
index 539b1e741..46062569d 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgEncryptedCheetahGenerator.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgEncryptedCheetahGenerator.py
@@ -11,7 +11,7 @@ while path != "/":
if os.path.basename(path) == "testsuite":
break
path = os.path.dirname(path)
-from common import can_skip, skipUnless
+from common import *
try:
from TestServer.TestPlugins.TestCfg.TestCfgCheetahGenerator import \
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgEncryptedGenerator.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgEncryptedGenerator.py
index 7e4a64996..71a7410da 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgEncryptedGenerator.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgEncryptedGenerator.py
@@ -14,9 +14,7 @@ while path != "/":
if os.path.basename(path) == "testsuite":
break
path = os.path.dirname(path)
-from common import XI_NAMESPACE, XI, inPy3k, call, builtins, u, can_skip, \
- skip, skipIf, skipUnless, Bcfg2TestCase, DBModelTestCase, syncdb, \
- patchIf, datastore, re_type
+from common import *
from TestServer.TestPlugins.TestCfg.Test_init import TestCfgGenerator
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgEncryptedGenshiGenerator.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgEncryptedGenshiGenerator.py
index 0375b8928..b447a9bb8 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgEncryptedGenshiGenerator.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgEncryptedGenshiGenerator.py
@@ -12,9 +12,7 @@ while path != "/":
if os.path.basename(path) == "testsuite":
break
path = os.path.dirname(path)
-from common import XI_NAMESPACE, XI, inPy3k, call, builtins, u, can_skip, \
- skip, skipIf, skipUnless, Bcfg2TestCase, DBModelTestCase, syncdb, \
- patchIf, datastore, re_type
+from common import *
try:
from TestServer.TestPlugins.TestCfg.TestCfgGenshiGenerator import \
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgExternalCommandVerifier.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgExternalCommandVerifier.py
index fde7ed722..11dbdd391 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgExternalCommandVerifier.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgExternalCommandVerifier.py
@@ -14,9 +14,7 @@ while path != "/":
if os.path.basename(path) == "testsuite":
break
path = os.path.dirname(path)
-from common import XI_NAMESPACE, XI, inPy3k, call, builtins, u, can_skip, \
- skip, skipIf, skipUnless, Bcfg2TestCase, DBModelTestCase, syncdb, \
- patchIf, datastore, re_type
+from common import *
from TestServer.TestPlugins.TestCfg.Test_init import TestCfgVerifier
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgGenshiGenerator.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgGenshiGenerator.py
index 2dd647352..baad10933 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgGenshiGenerator.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgGenshiGenerator.py
@@ -14,9 +14,7 @@ while path != "/":
if os.path.basename(path) == "testsuite":
break
path = os.path.dirname(path)
-from common import XI_NAMESPACE, XI, inPy3k, call, builtins, u, can_skip, \
- skip, skipIf, skipUnless, Bcfg2TestCase, DBModelTestCase, syncdb, \
- patchIf, datastore, re_type
+from common import *
from TestServer.TestPlugins.TestCfg.Test_init import TestCfgGenerator
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgInfoXML.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgInfoXML.py
index 88dd1f18f..839e9c3b8 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgInfoXML.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgInfoXML.py
@@ -14,9 +14,7 @@ while path != "/":
if os.path.basename(path) == "testsuite":
break
path = os.path.dirname(path)
-from common import XI_NAMESPACE, XI, inPy3k, call, builtins, u, can_skip, \
- skip, skipIf, skipUnless, Bcfg2TestCase, DBModelTestCase, syncdb, \
- patchIf, datastore, re_type
+from common import *
from TestServer.TestPlugins.TestCfg.Test_init import TestCfgInfo
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/Test_init.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/Test_init.py
index 11f06873d..8cfd69242 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/Test_init.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/Test_init.py
@@ -16,9 +16,7 @@ while path != "/":
if os.path.basename(path) == "testsuite":
break
path = os.path.dirname(path)
-from common import XI_NAMESPACE, XI, inPy3k, call, builtins, u, can_skip, \
- skip, skipIf, skipUnless, Bcfg2TestCase, DBModelTestCase, syncdb, \
- patchIf, datastore, re_type
+from common import *
from TestPlugin import TestSpecificData, TestEntrySet, TestGroupSpool, \
TestPullTarget
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestMetadata.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestMetadata.py
index 13e514e77..5610d9071 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestMetadata.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestMetadata.py
@@ -18,12 +18,11 @@ while path != "/":
if os.path.basename(path) == "testsuite":
break
path = os.path.dirname(path)
-from common import XI_NAMESPACE, XI, inPy3k, call, builtins, u, can_skip, \
- skip, skipIf, skipUnless, Bcfg2TestCase, DBModelTestCase, syncdb, \
- patchIf, datastore
+from common import *
from TestPlugin import TestXMLFileBacked, TestMetadata as _TestMetadata, \
TestStatistics, TestDatabaseBacked
+
def get_clients_test_tree():
return lxml.etree.XML('''
<Clients>
@@ -47,6 +46,7 @@ def get_clients_test_tree():
<Client name="client10" profile="group1" floating="true"/>
</Clients>''').getroottree()
+
def get_groups_test_tree():
return lxml.etree.XML('''
<Groups xmlns:xi="http://www.w3.org/2001/XInclude">
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProbes.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProbes.py
index 0ad92ca72..a1d41b693 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProbes.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProbes.py
@@ -15,9 +15,7 @@ while path != "/":
if os.path.basename(path) == "testsuite":
break
path = os.path.dirname(path)
-from common import XI_NAMESPACE, XI, inPy3k, call, builtins, u, can_skip, \
- skip, skipIf, skipUnless, Bcfg2TestCase, DBModelTestCase, syncdb, \
- patchIf, datastore
+from common import *
from Bcfg2.Server.Plugins.Probes import *
from TestPlugin import TestEntrySet, TestProbing, TestConnector, \
TestDatabaseBacked
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProperties.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProperties.py
index 883e88ba1..fb4773d75 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProperties.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProperties.py
@@ -14,9 +14,7 @@ while path != "/":
if os.path.basename(path) == "testsuite":
break
path = os.path.dirname(path)
-from common import XI_NAMESPACE, XI, inPy3k, call, builtins, u, can_skip, \
- skip, skipIf, skipUnless, Bcfg2TestCase, DBModelTestCase, syncdb, \
- patchIf, datastore
+from common import *
from TestPlugin import TestStructFile, TestConnector, TestPlugin, \
TestDirectoryBacked
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestSEModules.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestSEModules.py
index e18e2bfd6..8a148b353 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestSEModules.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestSEModules.py
@@ -14,9 +14,7 @@ while path != "/":
if os.path.basename(path) == "testsuite":
break
path = os.path.dirname(path)
-from common import XI_NAMESPACE, XI, inPy3k, call, builtins, u, can_skip, \
- skip, skipIf, skipUnless, Bcfg2TestCase, DBModelTestCase, syncdb, \
- patchIf, datastore
+from common import *
from TestPlugin import TestSpecificData, TestGroupSpool
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestTemplateHelper.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestTemplateHelper.py
index 18855a631..832857601 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestTemplateHelper.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestTemplateHelper.py
@@ -13,8 +13,7 @@ while path != "/":
if os.path.basename(path) == "testsuite":
break
path = os.path.dirname(path)
-from common import call, skip, skipIf, skipUnless, Bcfg2TestCase, patchIf, \
- datastore
+from common import *
from TestPlugin import TestDirectoryBacked, TestConnector, TestPlugin, \
TestFileBacked
diff --git a/testsuite/Testsrc/test_code_checks.py b/testsuite/Testsrc/test_code_checks.py
index ac4183576..481dd7fa3 100644
--- a/testsuite/Testsrc/test_code_checks.py
+++ b/testsuite/Testsrc/test_code_checks.py
@@ -13,7 +13,7 @@ while _path != '/':
if os.path.basename(_path) == "testsuite":
break
_path = os.path.dirname(_path)
-from common import can_skip, skip, skipIf, skipUnless, Bcfg2TestCase
+from common import *
# path to Bcfg2 src directory
srcpath = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..",
@@ -168,7 +168,6 @@ class TestPylint(Bcfg2TestCase):
full_blacklist = expand_path_dict(error_checks) + contingent_blacklist + \
blacklist
-
@skipIf(not os.path.exists(srcpath), "%s does not exist" % srcpath)
@skipIf(not os.path.exists(rcfile), "%s does not exist" % rcfile)
@skipUnless(HAS_PYLINT, "pylint not found, skipping")
diff --git a/testsuite/Testtools/__init__.py b/testsuite/Testtools/__init__.py
index 73687eb5f..993938e07 100644
--- a/testsuite/Testtools/__init__.py
+++ b/testsuite/Testtools/__init__.py
@@ -11,9 +11,7 @@ while path != "/":
if os.path.basename(path) == "testsuite":
break
path = os.path.dirname(path)
-from common import XI_NAMESPACE, XI, inPy3k, call, builtins, u, can_skip, \
- skip, skipIf, skipUnless, Bcfg2TestCase, DBModelTestCase, syncdb, \
- patchIf, datastore
+from common import *
TOOLSDIR = os.path.abspath(os.path.join(os.path.dirname(__file__),
"..", "..", "tools"))
diff --git a/testsuite/common.py b/testsuite/common.py
index 5d8157e55..0cb457461 100644
--- a/testsuite/common.py
+++ b/testsuite/common.py
@@ -1,19 +1,37 @@
+""" In order to make testing easier and more consistent, we provide a
+number of convenience functions, variables, and classes, for a wide
+variety of reasons. To import this module, first set up
+:ref:`development-unit-testing-relative-imports` and then simply do:
+
+.. code-block:: python
+
+ from common import *
+"""
+
import os
import re
import sys
+import codecs
import unittest
from mock import patch, MagicMock, _patch, DEFAULT
from Bcfg2.Compat import wraps
+#: The path to the Bcfg2 specification root for the tests. Using the
+#: root directory exposes a lot of potential problems with building
+#: paths.
datastore = "/"
+#: The XInclude namespace name
XI_NAMESPACE = "http://www.w3.org/2001/XInclude"
+
+#: The XInclude namespace in a format suitable for use in XPath
+#: expressions
XI = "{%s}" % XI_NAMESPACE
+#: Whether or not the tests are being run on Python 3.
+inPy3k = False
if sys.hexversion >= 0x03000000:
inPy3k = True
-else:
- inPy3k = False
try:
from django.core.management import setup_environ
@@ -33,45 +51,84 @@ try:
from mock import call
except ImportError:
def call(*args, **kwargs):
- """ the Mock call object is a fairly recent addition, but it's
- very very useful, so we create our own function to create Mock
- calls """
+ """ Analog to the Mock call object, which is a fairly recent
+ addition, but it's very very useful, so we create our own
+ function to create Mock calls"""
return (args, kwargs)
+#: The name of the builtin module, for mocking Python builtins. In
+#: Python 2, this is ``__builtin__``, in Python 3 ``builtins``. To
+#: patch a builtin, you must do something like:
+#:
+#: .. code-block:: python
+#:
+#: @patch("%s.open" % open)
+#: def test_something(self, mock_open):
+#: ...
+builtins = "__builtin__"
+
+
if inPy3k:
builtins = "builtins"
- def u(x):
- return x
+ def u(s):
+ """ Get a unicode string, whatever that means. In Python 2,
+ returns a unicode object; in Python 3, returns a str object.
+
+ :param s: The string to unicode-ify.
+ :type s: str
+ :returns: str or unicode """
+ return s
else:
- builtins = "__builtin__"
+ def u(s):
+ """ Get a unicode string, whatever that means. In Python 2,
+ returns a unicode object; in Python 3, returns a str object.
+
+ :param s: The string to unicode-ify.
+ :type s: str
+ :returns: str or unicode """
+ return codecs.unicode_escape_decode(s)[0]
- import codecs
- def u(x):
- return codecs.unicode_escape_decode(x)[0]
+#: Whether or not skipping tests is natively supported by
+#: :mod:`unittest`. If it isn't, then we have to make tests that
+#: would be skipped succeed instead.
+can_skip = False
if hasattr(unittest, "skip"):
can_skip = True
+
+ #: skip decorator from :func:`unittest.skip`
skip = unittest.skip
+
+ #: skipIf decorator from :func:`unittest.skipIf`
skipIf = unittest.skipIf
+
+ #: skipUnless decorator from :func:`unittest.skipUnless`
skipUnless = unittest.skipUnless
else:
# we can't actually skip tests, we just make them pass
can_skip = False
def skip(msg):
+ """ skip decorator used when :mod:`unittest` doesn't support
+ skipping tests. Replaces the decorated function with a
+ no-op. """
def decorator(func):
return lambda *args, **kwargs: None
return decorator
def skipIf(condition, msg):
+ """ skipIf decorator used when :mod:`unittest` doesn't support
+ skipping tests """
if not condition:
return lambda f: f
else:
return skip(msg)
def skipUnless(condition, msg):
+ """ skipUnless decorator used when :mod:`unittest` doesn't
+ support skipping tests """
if condition:
return lambda f: f
else:
@@ -114,6 +171,7 @@ def _count_diff_all_purpose(actual, expected):
result.append(diff)
return result
+
def _assertion(predicate, default_msg=None):
@wraps(predicate)
def inner(self, *args, **kwargs):
@@ -130,6 +188,7 @@ def _assertion(predicate, default_msg=None):
assert predicate(*args, **kwargs), msg
return inner
+
def _regex_matches(val, regex):
if hasattr(regex, 'search'):
return regex.search(val)
@@ -138,11 +197,23 @@ def _regex_matches(val, regex):
class Bcfg2TestCase(unittest.TestCase):
+ """ Base TestCase class that inherits from
+ :class:`unittest.TestCase`. This class does a few things:
+
+ * Adds :func:`assertXMLEqual`, a useful assertion method given all
+ the XML used by Bcfg2;
+
+ * Defines convenience methods that were (mostly) added in Python
+ 2.7.
+ """
if not hasattr(unittest.TestCase, "assertItemsEqual"):
# TestCase in Py3k lacks assertItemsEqual, but has the other
# convenience methods. this code is (mostly) cribbed from the
# py2.7 unittest library
def assertItemsEqual(self, expected_seq, actual_seq, msg=None):
+ """ Implementation of
+ :func:`unittest.TestCase.assertItemsEqual` for python
+ versions that lack it """
first_seq, second_seq = list(actual_seq), list(expected_seq)
differences = _count_diff_all_purpose(first_seq, second_seq)
@@ -191,6 +262,9 @@ class Bcfg2TestCase(unittest.TestCase):
"%s is not less than or equal to %s")
def assertXMLEqual(self, el1, el2, msg=None):
+ """ Test that the two XML trees given are equal. Both
+ elements and all children are expected to have ``name``
+ attributes. """
self.assertEqual(el1.tag, el2.tag, msg=msg)
self.assertEqual(el1.text, el2.text, msg=msg)
self.assertItemsEqual(el1.attrib, el2.attrib, msg=msg)
@@ -208,11 +282,12 @@ class Bcfg2TestCase(unittest.TestCase):
class DBModelTestCase(Bcfg2TestCase):
+ """ Test case class for Django database models """
models = []
@skipUnless(has_django, "Django not found, skipping")
def test_syncdb(self):
- # create the test database
+ """ Create the test database and sync the schema """
setup_environ(Bcfg2.settings)
import django.core.management
django.core.management.call_command("syncdb", interactive=False,
@@ -221,14 +296,16 @@ class DBModelTestCase(Bcfg2TestCase):
@skipUnless(has_django, "Django not found, skipping")
def test_cleandb(self):
- # ensure that we a) can connect to the database; b) start with
- # a clean database
+ """ Ensure that we a) can connect to the database; b) start
+ with a clean database """
for model in self.models:
model.objects.all().delete()
self.assertItemsEqual(list(model.objects.all()), [])
def syncdb(modeltest):
+ """ Given an instance of a :class:`DBModelTestCase` object, sync
+ and clean the database """
inst = modeltest(methodName='test_syncdb')
inst.test_syncdb()
inst.test_cleandb()
@@ -246,15 +323,39 @@ class _noop_patch(_patch):
class patchIf(object):
- """ perform conditional patching. this is necessary because some
- libraries might not be installed (e.g., selinux, pylibacl), and
- patching will barf on that. Other workarounds are not available
- to us; e.g., context managers aren't in python 2.4, and using
- inner functions doesn't work because python 2.6 applies all
- decorators at compile-time, not at run-time, so decorating inner
- functions does not prevent the decorators from being run. """
+ """ Decorator class to perform conditional patching. This is
+ necessary because some libraries might not be installed (e.g.,
+ selinux, pylibacl), and patching will barf on that. Other
+ workarounds are not available to us; e.g., context managers aren't
+ in python 2.4, and using inner functions doesn't work because
+ python 2.6 parses all decorators at compile-time, not at run-time,
+ so decorating inner functions does not prevent the decorators from
+ being run. """
+
def __init__(self, condition, target, new=DEFAULT, spec=None, create=False,
spec_set=None):
+ """
+ :param condition: The condition to evaluate to decide if the
+ patch will be applied.
+ :type condition: bool
+ :param target: The name of the target object to patch
+ :type target: str
+ :param new: The new object to replace the target with. If
+ this is omitted, a new :class:`mock.MagicMock` is
+ created and passed as an extra argument to the
+ decorated function.
+ :type new: any
+ :param spec: Spec passed to the MagicMock object if
+ ``patchIf`` is creating one for you.
+ :type spec: List of strings or existing object
+ :param create: Tell patch to create attributes on the fly.
+ See the documentation for :func:`mock.patch`
+ for more details on this.
+ :type create: bool
+ :param spec_set: Spec set passed to the MagicMock object if
+ ``patchIf`` is creating one for you.
+ :type spec_set: List of strings or existing object
+ """
self.condition = condition
self.target = target
@@ -286,6 +387,8 @@ class patchIf(object):
return _noop_patch(*args)(func)
+#: The type of compiled regular expression objects
+re_type = None
try:
re_type = re._pattern_type
except AttributeError: