From 1a3ced3f45423d79e08ca7d861e8118e8618d3b2 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Fri, 5 Oct 2012 11:57:16 -0400 Subject: wrote more detailed unit testing documentation --- doc/conf.py | 4 +- doc/development/testing.txt | 31 +- doc/development/unit-testing.txt | 354 ++++++++++++++++++++- .../TestClient/TestTools/TestPOSIX/TestDevice.py | 4 +- .../TestTools/TestPOSIX/TestDirectory.py | 4 +- .../TestClient/TestTools/TestPOSIX/TestFile.py | 4 +- .../TestClient/TestTools/TestPOSIX/TestHardlink.py | 4 +- .../TestTools/TestPOSIX/TestNonexistent.py | 4 +- .../TestClient/TestTools/TestPOSIX/TestSymlink.py | 4 +- .../TestClient/TestTools/TestPOSIX/Test__init.py | 4 +- .../TestClient/TestTools/TestPOSIX/Testbase.py | 7 +- testsuite/Testsrc/Testlib/TestEncryption.py | 3 +- testsuite/Testsrc/Testlib/TestOptions.py | 4 +- .../Testlib/TestServer/TestPlugin/Testbase.py | 3 +- .../TestServer/TestPlugin/Testexceptions.py | 3 +- .../Testlib/TestServer/TestPlugin/Testhelpers.py | 7 +- .../TestServer/TestPlugin/Testinterfaces.py | 3 +- .../TestPlugins/TestCfg/TestCfgCheetahGenerator.py | 4 +- .../TestCfg/TestCfgEncryptedCheetahGenerator.py | 2 +- .../TestCfg/TestCfgEncryptedGenerator.py | 4 +- .../TestCfg/TestCfgEncryptedGenshiGenerator.py | 4 +- .../TestCfg/TestCfgExternalCommandVerifier.py | 4 +- .../TestPlugins/TestCfg/TestCfgGenshiGenerator.py | 4 +- .../TestPlugins/TestCfg/TestCfgInfoXML.py | 4 +- .../TestServer/TestPlugins/TestCfg/Test_init.py | 4 +- .../Testlib/TestServer/TestPlugins/TestMetadata.py | 6 +- .../Testlib/TestServer/TestPlugins/TestProbes.py | 4 +- .../TestServer/TestPlugins/TestProperties.py | 4 +- .../TestServer/TestPlugins/TestSEModules.py | 4 +- .../TestServer/TestPlugins/TestTemplateHelper.py | 3 +- testsuite/Testsrc/test_code_checks.py | 3 +- testsuite/Testtools/__init__.py | 4 +- testsuite/common.py | 145 +++++++-- 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 `_ . +For more details please visit `Tracking Development Releases of Bcfg2 +`_ +. 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 +`_ 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 `, +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__``, 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(''' @@ -47,6 +46,7 @@ def get_clients_test_tree(): ''').getroottree() + def get_groups_test_tree(): return lxml.etree.XML(''' 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: -- cgit v1.2.3-1-g7c22