summaryrefslogtreecommitdiffstats
path: root/testsuite
diff options
context:
space:
mode:
Diffstat (limited to 'testsuite')
-rw-r--r--testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestFile.py13
-rw-r--r--testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/Test__init.py43
-rw-r--r--testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/Testbase.py29
-rw-r--r--testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIXUsers.py28
-rw-r--r--testsuite/Testsrc/Testlib/TestClient/TestTools/Test_init.py70
-rw-r--r--testsuite/Testsrc/Testlib/TestEncryption.py199
-rw-r--r--testsuite/Testsrc/Testlib/TestLogger.py63
-rw-r--r--testsuite/Testsrc/Testlib/TestOptions.py236
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestCache.py54
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestEncryption.py149
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testbase.py54
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testhelpers.py1225
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testinterfaces.py17
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestACL.py223
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestBundler.py111
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgAuthorizedKeysGenerator.py55
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgCheetahGenerator.py59
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgEncryptedCheetahGenerator.py23
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgEncryptedGenerator.py89
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgEncryptedGenshiGenerator.py21
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgExternalCommandVerifier.py31
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgGenshiGenerator.py225
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgInfoXML.py41
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgPrivateKeyCreator.py349
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgPublicKeyCreator.py2
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/Test_init.py237
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestDecisions.py60
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestDefaults.py29
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestMetadata.py326
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProbes.py680
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProperties.py326
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestRules.py259
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestTemplateHelper.py2
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestStatistics.py (renamed from testsuite/Testsrc/Testlib/TestStatistics.py)2
-rw-r--r--testsuite/Testsrc/Testsbin/bcfg2-crypt/all-basic.xml12
-rw-r--r--testsuite/Testsrc/Testsbin/bcfg2-crypt/basic-des-cbc.crypt1
-rw-r--r--testsuite/Testsrc/Testsbin/bcfg2-crypt/basic.crypt1
-rw-r--r--testsuite/Testsrc/Testsbin/bcfg2-crypt/basic2.crypt1
-rw-r--r--testsuite/Testsrc/Testsbin/bcfg2-crypt/bogus-forced.xml5
-rw-r--r--testsuite/Testsrc/Testsbin/bcfg2-crypt/bogus.xml5
-rw-r--r--testsuite/Testsrc/Testsbin/bcfg2-crypt/complex.crypt1
-rw-r--r--testsuite/Testsrc/Testsbin/bcfg2-crypt/plaintext9
-rw-r--r--testsuite/Testsrc/Testsbin/bcfg2-crypt/plaintext-all.xml12
-rw-r--r--testsuite/Testsrc/Testsbin/bcfg2-crypt/plaintext-xpath.xml12
-rw-r--r--testsuite/Testsrc/Testsbin/bcfg2-crypt/plaintext.xml18
-rw-r--r--testsuite/Testsrc/Testsbin/bcfg2-crypt/plaintext2.xml18
-rw-r--r--testsuite/Testsrc/Testsbin/test_bcfg2_crypt.py390
-rw-r--r--testsuite/Testsrc/test_code_checks.py21
-rw-r--r--testsuite/common.py239
-rwxr-xr-xtestsuite/install.sh7
-rw-r--r--testsuite/pylintrc.conf2
-rw-r--r--testsuite/requirements.txt2
52 files changed, 2965 insertions, 3121 deletions
diff --git a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestFile.py b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestFile.py
index 8f933e08f..31e297888 100644
--- a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestFile.py
+++ b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestFile.py
@@ -55,8 +55,8 @@ class TestPOSIXFile(TestPOSIXTool):
def test_get_data(self):
orig_entry = lxml.etree.Element("Path", name="/test", type="file")
- ptool = self.get_obj(setup=dict(encoding="ascii", ppath='/',
- max_copies=5))
+ Bcfg2.Options.setup.encoding = "ascii"
+ ptool = self.get_obj()
entry = copy.deepcopy(orig_entry)
entry.text = b64encode("test")
@@ -91,8 +91,7 @@ class TestPOSIXFile(TestPOSIXTool):
@patch("Bcfg2.Client.Tools.POSIX.base.POSIXTool.verify")
def test_verify(self, mock_verify, mock_open):
entry = lxml.etree.Element("Path", name="/test", type="file")
- ptool = self.get_obj(setup=dict(interactive=False, ppath='/',
- max_copies=5))
+ ptool = self.get_obj()
ptool._exists = Mock()
ptool._get_data = Mock()
ptool._get_diffs = Mock()
@@ -223,8 +222,8 @@ class TestPOSIXFile(TestPOSIXTool):
group='root')
orig_entry.text = "test"
ondisk = "test2"
- ptool = self.get_obj(setup=dict(encoding="utf-8", ppath='/',
- max_copies=5))
+ Bcfg2.Options.setup.encoding = "utf-8"
+ ptool = self.get_obj()
ptool._get_data = Mock()
ptool._diff = Mock()
ptool._is_string = Mock()
@@ -312,7 +311,7 @@ class TestPOSIXFile(TestPOSIXTool):
# non-sensitive, interactive with unicode data
entry = reset()
entry.text = u("tëst")
- encoded = entry.text.encode(ptool.setup['encoding'])
+ encoded = entry.text.encode(Bcfg2.Options.setup.encoding)
ptool._diff.return_value = ["-test2", "+tëst"]
ptool._get_data.return_value = (encoded, False)
ptool._get_diffs(entry, interactive=True)
diff --git a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/Test__init.py b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/Test__init.py
index f01082e86..adc2032b7 100644
--- a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/Test__init.py
+++ b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/Test__init.py
@@ -49,7 +49,6 @@ class TestPOSIX(TestTool):
mock_canVerify.assert_called_with(posix, entry)
# next, test fully_specified failure
- posix.logger.error.reset_mock()
mock_canVerify.reset_mock()
mock_canVerify.return_value = True
mock_fully_spec = Mock()
@@ -59,17 +58,14 @@ class TestPOSIX(TestTool):
self.assertFalse(posix.canVerify(entry))
mock_canVerify.assert_called_with(posix, entry)
mock_fully_spec.assert_called_with(entry)
- self.assertTrue(posix.logger.error.called)
# finally, test success
- posix.logger.error.reset_mock()
mock_canVerify.reset_mock()
mock_fully_spec.reset_mock()
mock_fully_spec.return_value = True
self.assertTrue(posix.canVerify(entry))
mock_canVerify.assert_called_with(posix, entry)
mock_fully_spec.assert_called_with(entry)
- self.assertFalse(posix.logger.error.called)
@patch("Bcfg2.Client.Tools.Tool.canInstall")
def test_canInstall(self, mock_canInstall):
@@ -82,7 +78,6 @@ class TestPOSIX(TestTool):
mock_canInstall.assert_called_with(posix, entry)
# next, test fully_specified failure
- posix.logger.error.reset_mock()
mock_canInstall.reset_mock()
mock_canInstall.return_value = True
mock_fully_spec = Mock()
@@ -92,17 +87,14 @@ class TestPOSIX(TestTool):
self.assertFalse(posix.canInstall(entry))
mock_canInstall.assert_called_with(posix, entry)
mock_fully_spec.assert_called_with(entry)
- self.assertTrue(posix.logger.error.called)
# finally, test success
- posix.logger.error.reset_mock()
mock_canInstall.reset_mock()
mock_fully_spec.reset_mock()
mock_fully_spec.return_value = True
self.assertTrue(posix.canInstall(entry))
mock_canInstall.assert_called_with(posix, entry)
mock_fully_spec.assert_called_with(entry)
- self.assertFalse(posix.logger.error.called)
def test_InstallPath(self):
posix = self.get_obj()
@@ -127,15 +119,17 @@ class TestPOSIX(TestTool):
mock_verify.reset_mock()
mock_verify.return_value = False
- posix.setup.__getitem__.return_value = True
+ Bcfg2.Options.setup.interactive = True
self.assertFalse(posix.VerifyPath(entry, modlist))
self.assertIsNotNone(entry.get('qtext'))
@patch('os.remove')
def test_prune_old_backups(self, mock_remove):
entry = lxml.etree.Element("Path", name="/etc/foo", type="file")
- setup = dict(ppath='/', max_copies=5, paranoid=True)
- posix = self.get_obj(setup=setup)
+ Bcfg2.Options.setup.paranoid_path = '/'
+ Bcfg2.Options.setup.paranoid_copies = 5
+ Bcfg2.Options.setup.paranoid = True
+ posix = self.get_obj()
remove = ["_etc_foo_2012-07-20T04:13:22.364989",
"_etc_foo_2012-07-31T04:13:23.894958",
@@ -152,9 +146,8 @@ class TestPOSIX(TestTool):
def inner(mock_listdir):
mock_listdir.side_effect = OSError
posix._prune_old_backups(entry)
- self.assertTrue(posix.logger.error.called)
self.assertFalse(mock_remove.called)
- mock_listdir.assert_called_with(setup['ppath'])
+ mock_listdir.assert_called_with(Bcfg2.Options.setup.paranoid_path)
mock_listdir.reset_mock()
mock_remove.reset_mock()
@@ -162,23 +155,23 @@ class TestPOSIX(TestTool):
mock_listdir.return_value = keep + remove
posix._prune_old_backups(entry)
- mock_listdir.assert_called_with(setup['ppath'])
+ mock_listdir.assert_called_with(Bcfg2.Options.setup.paranoid_path)
self.assertItemsEqual(mock_remove.call_args_list,
- [call(os.path.join(setup['ppath'], p))
+ [call(os.path.join(Bcfg2.Options.setup.paranoid_path,
+ p))
for p in remove])
mock_listdir.reset_mock()
mock_remove.reset_mock()
mock_remove.side_effect = OSError
- posix.logger.error.reset_mock()
# test to ensure that we call os.remove() for all files that
# need to be removed even if we get an error
posix._prune_old_backups(entry)
- mock_listdir.assert_called_with(setup['ppath'])
+ mock_listdir.assert_called_with(Bcfg2.Options.setup.paranoid_path)
self.assertItemsEqual(mock_remove.call_args_list,
- [call(os.path.join(setup['ppath'], p))
+ [call(os.path.join(Bcfg2.Options.setup.paranoid_path,
+ p))
for p in remove])
- self.assertTrue(posix.logger.error.called)
inner()
@@ -186,8 +179,10 @@ class TestPOSIX(TestTool):
@patch("os.path.isdir")
def test_paranoid_backup(self, mock_isdir, mock_copy):
entry = lxml.etree.Element("Path", name="/etc/foo", type="file")
- setup = dict(ppath='/', max_copies=5, paranoid=False)
- posix = self.get_obj(setup=setup)
+ Bcfg2.Options.setup.paranoid_path = '/'
+ Bcfg2.Options.setup.paranoid_copies = 5
+ Bcfg2.Options.setup.paranoid = False
+ posix = self.get_obj()
posix._prune_old_backups = Mock()
# paranoid false globally
@@ -196,9 +191,7 @@ class TestPOSIX(TestTool):
self.assertFalse(mock_copy.called)
# paranoid false on the entry
- setup['paranoid'] = True
- posix = self.get_obj(setup=setup)
- posix._prune_old_backups = Mock()
+ Bcfg2.Options.setup.paranoid = True
def reset():
mock_isdir.reset_mock()
@@ -238,6 +231,6 @@ class TestPOSIX(TestTool):
# just test it good enough
self.assertEqual(mock_copy.call_args[0][0],
entry.get("name"))
- bkupnam = os.path.join(setup['ppath'],
+ bkupnam = os.path.join(Bcfg2.Options.setup.paranoid_path,
entry.get('name').replace('/', '_')) + '_'
self.assertEqual(bkupnam, mock_copy.call_args[0][1][:len(bkupnam)])
diff --git a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/Testbase.py b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/Testbase.py
index ab6e2fe54..ea4ca3f5f 100644
--- a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/Testbase.py
+++ b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/Testbase.py
@@ -197,7 +197,8 @@ class TestPOSIXTool(TestTool):
@patch("os.chown")
@patch("os.chmod")
@patch("os.utime")
- def test_set_perms(self, mock_utime, mock_chmod, mock_chown):
+ @patch("os.geteuid")
+ def test_set_perms(self, mock_geteuid, mock_utime, mock_chmod, mock_chown):
ptool = self.get_obj()
ptool._norm_entry_uid = Mock()
ptool._norm_entry_gid = Mock()
@@ -211,7 +212,12 @@ class TestPOSIXTool(TestTool):
mock_chmod.reset_mock()
mock_chown.reset_mock()
mock_utime.reset_mock()
+ mock_geteuid.reset_mock()
+ # pretend to run as root
+ mock_geteuid.return_value = 0
+
+ # test symlink -- no owner, group, permissions
entry = lxml.etree.Element("Path", name="/etc/foo", to="/etc/bar",
type="symlink")
ptool._set_acls.return_value = True
@@ -220,12 +226,12 @@ class TestPOSIXTool(TestTool):
ptool._set_secontext.assert_called_with(entry, path=entry.get("name"))
ptool._set_acls.assert_called_with(entry, path=entry.get("name"))
+ # test file with owner, group, permissions
+ reset()
entry = lxml.etree.Element("Path", name="/etc/foo", owner="owner",
group="group", mode="644", type="file")
ptool._norm_entry_uid.return_value = 10
ptool._norm_entry_gid.return_value = 100
-
- reset()
self.assertTrue(ptool._set_perms(entry))
ptool._norm_entry_uid.assert_called_with(entry)
ptool._norm_entry_gid.assert_called_with(entry)
@@ -236,6 +242,23 @@ class TestPOSIXTool(TestTool):
ptool._set_secontext.assert_called_with(entry, path=entry.get("name"))
ptool._set_acls.assert_called_with(entry, path=entry.get("name"))
+ # test file with owner, group, permissions, run as non-root
+ mock_geteuid.return_value = 1000
+ reset()
+ entry = lxml.etree.Element("Path", name="/etc/foo", owner="owner",
+ group="group", mode="644", type="file")
+ self.assertTrue(ptool._set_perms(entry))
+ self.assertFalse(ptool._norm_entry_uid.called)
+ self.assertFalse(ptool._norm_entry_gid.called)
+ self.assertFalse(mock_chown.called)
+ mock_chmod.assert_called_with(entry.get("name"),
+ int(entry.get("mode"), 8))
+ self.assertFalse(mock_utime.called)
+ ptool._set_secontext.assert_called_with(entry, path=entry.get("name"))
+ ptool._set_acls.assert_called_with(entry, path=entry.get("name"))
+ mock_geteuid.return_value = 0
+
+ # test with mtime
reset()
mtime = 1344459042
entry.set("mtime", str(mtime))
diff --git a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIXUsers.py b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIXUsers.py
index c207900f1..9647413b6 100644
--- a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIXUsers.py
+++ b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIXUsers.py
@@ -24,18 +24,16 @@ from TestTools.Test_init import TestTool
class TestPOSIXUsers(TestTool):
test_obj = POSIXUsers
- def get_obj(self, logger=None, setup=None, config=None):
- if setup is None:
- setup = MagicMock()
- def getitem(key):
- if key == 'encoding':
- return 'UTF-8'
- else:
- return []
-
- setup.__getitem__.side_effect = getitem
+ def setUp(self):
+ TestTool.setUp(self)
+ set_setup_default('uid_whitelist')
+ set_setup_default('uid_blacklist')
+ set_setup_default('gid_whitelist')
+ set_setup_default('gid_blacklist')
+ set_setup_default('encoding', 'UTF-8')
- return TestTool.get_obj(self, logger, setup, config)
+ def get_obj(self, config=None):
+ return TestTool.get_obj(self, config)
@patch("pwd.getpwall")
@patch("grp.getgrall")
@@ -141,10 +139,9 @@ class TestPOSIXUsers(TestTool):
users.set_defaults['POSIXUser'] = Mock()
users.set_defaults['POSIXUser'].side_effect = lambda e: e
- states = dict()
- self.assertEqual(users.Inventory(states),
+ self.assertEqual(users.Inventory(),
mock_Inventory.return_value)
- mock_Inventory.assert_called_with(users, states, config.getchildren())
+ mock_Inventory.assert_called_with(users, config.getchildren())
lxml.etree.SubElement(orig_bundle, "POSIXGroup", name="test")
self.assertXMLEqual(orig_bundle, bundle)
@@ -306,9 +303,8 @@ class TestPOSIXUsers(TestTool):
entries = [lxml.etree.Element("POSIXUser", name="test"),
lxml.etree.Element("POSIXGroup", name="test"),
lxml.etree.Element("POSIXUser", name="test2")]
- states = dict()
- users.Install(entries, states)
+ states = users.Install(entries)
self.assertItemsEqual(entries, states.keys())
for state in states.values():
self.assertEqual(state, users._install.return_value)
diff --git a/testsuite/Testsrc/Testlib/TestClient/TestTools/Test_init.py b/testsuite/Testsrc/Testlib/TestClient/TestTools/Test_init.py
index 390b92608..0c059b5f3 100644
--- a/testsuite/Testsrc/Testlib/TestClient/TestTools/Test_init.py
+++ b/testsuite/Testsrc/Testlib/TestClient/TestTools/Test_init.py
@@ -21,24 +21,17 @@ from common import *
class TestTool(Bcfg2TestCase):
test_obj = Tool
- def get_obj(self, logger=None, setup=None, config=None):
+ def setUp(self):
+ set_setup_default('command_timeout')
+ set_setup_default('interactive', False)
+
+ def get_obj(self, config=None):
if config is None:
config = lxml.etree.Element("Configuration")
- if not logger:
- def print_msg(msg):
- print(msg)
- logger = Mock()
- logger.error = Mock(side_effect=print_msg)
- logger.warning = Mock(side_effect=print_msg)
- logger.info = Mock(side_effect=print_msg)
- logger.debug = Mock(side_effect=print_msg)
- if not setup:
- setup = MagicMock()
- if 'command_timeout' not in setup:
- setup['command_timeout'] = None
+
execs = self.test_obj.__execs__
self.test_obj.__execs__ = []
- rv = self.test_obj(logger, setup, config)
+ rv = self.test_obj(config)
self.test_obj.__execs__ = execs
return rv
@@ -48,10 +41,12 @@ class TestTool(Bcfg2TestCase):
@patch("%s.%s._analyze_config" % (self.test_obj.__module__,
self.test_obj.__name__))
def inner(mock_analyze_config, mock_check_execs):
- t = self.get_obj()
+ self.get_obj()
mock_analyze_config.assert_called_with()
mock_check_execs.assert_called_with()
+ inner()
+
def test__analyze_config(self):
t = self.get_obj()
t.getSupportedEntries = Mock()
@@ -166,14 +161,12 @@ class TestTool(Bcfg2TestCase):
self.assertItemsEqual(states, expected_states)
self.assertEqual(t.extra, t.FindExtra.return_value)
- actual_states = dict()
- t.Inventory(actual_states, structures=[bundle1, bundle2])
+ actual_states = t.Inventory(structures=[bundle1, bundle2])
perform_assertions(actual_states)
reset()
- actual_states = dict()
t.config = config
- t.Inventory(actual_states)
+ actual_states = t.Inventory()
perform_assertions(actual_states)
def test_Install(self):
@@ -199,9 +192,8 @@ class TestTool(Bcfg2TestCase):
expected_states.update(dict([(e, t.InstallService.return_value)
for e in entries if e.tag == "Service"]))
- actual_states = dict()
t.modified = []
- t.Install(entries, actual_states)
+ actual_states = t.Install(entries)
self.assertItemsEqual(t.InstallPath.call_args_list,
[call(e) for e in entries if e.tag == "Path"])
self.assertItemsEqual(t.InstallPackage.call_args_list,
@@ -385,8 +377,7 @@ class TestPkgTool(TestTool):
# test single-pass install success
reset()
pt.cmd.run.return_value = True
- states = dict([(p, False) for p in packages])
- pt.Install(packages, states)
+ states = pt.Install(packages)
pt._get_package_command.assert_called_with(packages)
pt.cmd.run.assert_called_with([p.get("name") for p in packages])
self.assertItemsEqual(states,
@@ -406,8 +397,7 @@ class TestPkgTool(TestTool):
pt.VerifyPackage.side_effect = lambda p, m: p.get("name") == "bar"
pt.cmd.run.side_effect = run
- states = dict([(p, False) for p in packages])
- pt.Install(packages, states)
+ states = pt.Install(packages)
pt._get_package_command.assert_any_call(packages)
for pkg in packages:
pt.VerifyPackage.assert_any_call(pkg, [])
@@ -551,15 +541,15 @@ class TestSvcTool(TestTool):
@patch("Bcfg2.Client.prompt")
def test_BundleUpdated(self, mock_prompt):
- st = self.get_obj(setup=dict(interactive=False,
- servicemode='default'))
+ Bcfg2.Options.setup.service_mode = 'default'
+ Bcfg2.Options.setup.interactive = False
+ st = self.get_obj()
st.handlesEntry = Mock()
st.handlesEntry.side_effect = lambda e: e.tag == "Service"
st.stop_service = Mock()
- st.stop_service.return_value = 0
+ st.stop_service.return_value = True
st.restart_service = Mock()
- st.restart_service.side_effect = lambda e: \
- int(e.get("name") != "failed")
+ st.restart_service.side_effect = lambda e: e.get("name") != "failed"
def reset():
st.handlesEntry.reset_mock()
@@ -593,8 +583,7 @@ class TestSvcTool(TestTool):
# test in non-interactive mode
reset()
- states = dict()
- st.BundleUpdated(bundle, states)
+ states = st.BundleUpdated(bundle)
self.assertItemsEqual(st.handlesEntry.call_args_list,
[call(e) for e in entries])
st.stop_service.assert_called_with(stop)
@@ -606,9 +595,8 @@ class TestSvcTool(TestTool):
# test in interactive mode
reset()
mock_prompt.side_effect = lambda p: "interactive2" not in p
- st.setup['interactive'] = True
- states = dict()
- st.BundleUpdated(bundle, states)
+ Bcfg2.Options.setup.interactive = True
+ states = st.BundleUpdated(bundle)
self.assertItemsEqual(st.handlesEntry.call_args_list,
[call(e) for e in entries])
st.stop_service.assert_called_with(stop)
@@ -620,10 +608,9 @@ class TestSvcTool(TestTool):
# test in build mode
reset()
- st.setup['interactive'] = False
- st.setup['servicemode'] = 'build'
- states = dict()
- st.BundleUpdated(bundle, states)
+ Bcfg2.Options.setup.interactive = False
+ Bcfg2.Options.setup.service_mode = 'build'
+ states = st.BundleUpdated(bundle)
self.assertItemsEqual(st.handlesEntry.call_args_list,
[call(e) for e in entries])
self.assertItemsEqual(st.stop_service.call_args_list,
@@ -639,10 +626,9 @@ class TestSvcTool(TestTool):
services = install + [lxml.etree.Element("Service", type="test",
name="bar", install="false")]
st = self.get_obj()
- states = Mock()
- self.assertEqual(st.Install(services, states),
+ self.assertEqual(st.Install(services),
mock_Install.return_value)
- mock_Install.assert_called_with(st, install, states)
+ mock_Install.assert_called_with(st, install)
def test_InstallService(self):
st = self.get_obj()
diff --git a/testsuite/Testsrc/Testlib/TestEncryption.py b/testsuite/Testsrc/Testlib/TestEncryption.py
deleted file mode 100644
index c03aa66e1..000000000
--- a/testsuite/Testsrc/Testlib/TestEncryption.py
+++ /dev/null
@@ -1,199 +0,0 @@
-# -*- coding: utf-8 -*-
-import os
-import sys
-from Bcfg2.Compat import b64decode
-from mock import Mock, MagicMock, patch
-
-# 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)
-from common import *
-
-try:
- from Bcfg2.Encryption import *
- HAS_CRYPTO = True
-except ImportError:
- HAS_CRYPTO = False
-
-
-if can_skip or HAS_CRYPTO:
- class TestEncryption(Bcfg2TestCase):
- plaintext = """foo bar
-baz
-\t\tquux
-""" + "a" * 16384 # 16K is completely arbitrary
- iv = "0123456789ABCDEF"
- salt = "01234567"
- algo = "des_cbc"
-
- @skipUnless(HAS_CRYPTO, "Encryption libraries not found")
- def setUp(self):
- pass
-
- def test_str_crypt(self):
- """ test str_encrypt/str_decrypt """
- key = "a simple key"
-
- # simple symmetrical test with no options
- crypted = str_encrypt(self.plaintext, key)
- self.assertEqual(self.plaintext, str_decrypt(crypted, key))
-
- # symmetrical test with lots of options
- crypted = str_encrypt(self.plaintext, key,
- iv=self.iv, salt=self.salt,
- algorithm=self.algo)
- self.assertEqual(self.plaintext,
- str_decrypt(crypted, key, iv=self.iv,
- algorithm=self.algo))
-
- # test that different algorithms are actually used
- self.assertNotEqual(str_encrypt(self.plaintext, key),
- str_encrypt(self.plaintext, key,
- algorithm=self.algo))
-
- # test that different keys are actually used
- self.assertNotEqual(str_encrypt(self.plaintext, key),
- str_encrypt(self.plaintext, "different key"))
-
- # test that different IVs are actually used
- self.assertNotEqual(str_encrypt(self.plaintext, key, iv=self.iv),
- str_encrypt(self.plaintext, key))
-
- # test that errors are raised on bad decrypts
- crypted = str_encrypt(self.plaintext, key, algorithm=self.algo)
- self.assertRaises(EVPError, str_decrypt,
- crypted, "bogus key", algorithm=self.algo)
- self.assertRaises(EVPError, str_decrypt,
- crypted, key) # bogus algorithm
-
- def test_ssl_crypt(self):
- """ test ssl_encrypt/ssl_decrypt """
- passwd = "a simple passphrase"
-
- # simple symmetrical test
- crypted = ssl_encrypt(self.plaintext, passwd)
- self.assertEqual(self.plaintext, ssl_decrypt(crypted, passwd))
-
- # more complex symmetrical test
- crypted = ssl_encrypt(self.plaintext, passwd, algorithm=self.algo,
- salt=self.salt)
- self.assertEqual(self.plaintext,
- ssl_decrypt(crypted, passwd, algorithm=self.algo))
-
- # test that different algorithms are actually used
- self.assertNotEqual(ssl_encrypt(self.plaintext, passwd),
- ssl_encrypt(self.plaintext, passwd,
- algorithm=self.algo))
-
- # test that different passwords are actually used
- self.assertNotEqual(ssl_encrypt(self.plaintext, passwd),
- ssl_encrypt(self.plaintext, "different pass"))
-
- # there's no reasonable test we can do to see if the
- # output is base64-encoded, unfortunately, but if it's
- # obviously not we fail
- crypted = ssl_encrypt(self.plaintext, passwd)
- self.assertRegexpMatches(crypted, r'^[A-Za-z0-9+/]+[=]{0,2}$')
-
- # test that errors are raised on bad decrypts
- crypted = ssl_encrypt(self.plaintext, passwd,
- algorithm=self.algo)
- self.assertRaises(EVPError, ssl_decrypt,
- crypted, "bogus passwd", algorithm=self.algo)
- self.assertRaises(EVPError, ssl_decrypt,
- crypted, passwd) # bogus algorithm
-
- def test_get_algorithm(self):
- setup = Mock()
- # we don't care what the default is, as long as there is
- # one
- setup.cfp.get.return_value = ALGORITHM
- self.assertRegexpMatches(get_algorithm(setup),
- r'^[a-z0-9]+_[a-z0-9_]+$')
- setup.cfp.get.assert_called_with(CFG_SECTION, CFG_ALGORITHM,
- default=ALGORITHM)
-
- setup.cfp.get.return_value = self.algo
- self.assertEqual(get_algorithm(setup), self.algo)
- setup.cfp.get.assert_called_with(CFG_SECTION, CFG_ALGORITHM,
- default=ALGORITHM)
-
- # test that get_algorithm converts algorithms given in
- # OpenSSL style to M2Crypto style
- setup.cfp.get.return_value = "DES-EDE3-CFB8"
- self.assertEqual(get_algorithm(setup), "des_ede3_cfb8")
- setup.cfp.get.assert_called_with(CFG_SECTION, CFG_ALGORITHM,
- default=ALGORITHM)
-
- def test_get_passphrases(self):
- setup = Mock()
- setup.cfp.has_section.return_value = False
- self.assertEqual(get_passphrases(setup), dict())
-
- setup.cfp.has_section.return_value = True
- setup.cfp.options.return_value = ["foo", "bar", CFG_ALGORITHM]
- setup.cfp.get.return_value = "passphrase"
- self.assertItemsEqual(get_passphrases(setup),
- dict(foo="passphrase",
- bar="passphrase"))
-
- @patch("Bcfg2.Encryption.get_passphrases")
- def test_bruteforce_decrypt(self, mock_passphrases):
- passwd = "a simple passphrase"
- crypted = ssl_encrypt(self.plaintext, passwd)
- setup = Mock()
-
- # test with no passphrases given nor in config
- mock_passphrases.return_value = dict()
- self.assertRaises(EVPError,
- bruteforce_decrypt,
- crypted, setup=setup)
- mock_passphrases.assert_called_with(setup)
-
- # test with good passphrase given in function call
- mock_passphrases.reset_mock()
- self.assertEqual(self.plaintext,
- bruteforce_decrypt(crypted,
- passphrases=["bogus pass",
- passwd,
- "also bogus"]))
- self.assertFalse(mock_passphrases.called)
-
- # test with no good passphrase given nor in config
- mock_passphrases.reset_mock()
- self.assertRaises(EVPError,
- bruteforce_decrypt,
- crypted, passphrases=["bogus", "also bogus"])
- self.assertFalse(mock_passphrases.called)
-
- # test with good passphrase in config file
- mock_passphrases.reset_mock()
- mock_passphrases.return_value = dict(bogus="bogus",
- real=passwd,
- bogus2="also bogus")
- self.assertEqual(self.plaintext,
- bruteforce_decrypt(crypted, setup=setup))
- mock_passphrases.assert_called_with(setup)
-
- # test that passphrases given in function call take
- # precedence over config
- mock_passphrases.reset_mock()
- self.assertRaises(EVPError,
- bruteforce_decrypt,
- crypted, setup=setup,
- passphrases=["bogus", "also bogus"])
- self.assertFalse(mock_passphrases.called)
-
- # test that different algorithms are used
- mock_passphrases.reset_mock()
- crypted = ssl_encrypt(self.plaintext, passwd, algorithm=self.algo)
- self.assertEqual(self.plaintext,
- bruteforce_decrypt(crypted, setup=setup,
- algorithm=self.algo))
diff --git a/testsuite/Testsrc/Testlib/TestLogger.py b/testsuite/Testsrc/Testlib/TestLogger.py
new file mode 100644
index 000000000..1baea2f35
--- /dev/null
+++ b/testsuite/Testsrc/Testlib/TestLogger.py
@@ -0,0 +1,63 @@
+import os
+import sys
+import logging
+from mock import Mock
+from Bcfg2.Logger import *
+
+# 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)
+from common import *
+
+
+class TestDebuggable(Bcfg2TestCase):
+ test_obj = Debuggable
+
+ def setUp(self):
+ set_setup_default('debug', False)
+
+ def get_obj(self):
+ return self.test_obj()
+
+ def test__init(self):
+ d = self.get_obj()
+ self.assertIsInstance(d.logger, logging.Logger)
+ self.assertFalse(d.debug_flag)
+
+ def test_set_debug(self):
+ d = self.get_obj()
+ self.assertEqual(True, d.set_debug(True))
+ self.assertEqual(d.debug_flag, True)
+
+ self.assertEqual(False, d.set_debug(False))
+ self.assertEqual(d.debug_flag, False)
+
+ def test_toggle_debug(self):
+ d = self.get_obj()
+ d.set_debug = Mock()
+ orig = d.debug_flag
+ self.assertEqual(d.toggle_debug(),
+ d.set_debug.return_value)
+ d.set_debug.assert_called_with(not orig)
+
+ def test_debug_log(self):
+ d = self.get_obj()
+ d.logger = Mock()
+ d.debug_flag = False
+ d.debug_log("test")
+ self.assertFalse(d.logger.error.called)
+
+ d.logger.reset_mock()
+ d.debug_log("test", flag=True)
+ self.assertTrue(d.logger.error.called)
+
+ d.logger.reset_mock()
+ d.debug_flag = True
+ d.debug_log("test")
+ self.assertTrue(d.logger.error.called)
diff --git a/testsuite/Testsrc/Testlib/TestOptions.py b/testsuite/Testsrc/Testlib/TestOptions.py
deleted file mode 100644
index dc91a499b..000000000
--- a/testsuite/Testsrc/Testlib/TestOptions.py
+++ /dev/null
@@ -1,236 +0,0 @@
-import os
-import sys
-from mock import Mock, MagicMock, patch
-from Bcfg2.Options import *
-from Bcfg2.Compat import ConfigParser
-
-# 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)
-from common import *
-
-class TestDefaultConfigParser(Bcfg2TestCase):
- @patch("%s.ConfigParser.get" % ConfigParser.__name__)
- def test_get(self, mock_get):
- dcp = DefaultConfigParser()
- mock_get.return_value = "foo"
- self.assertEqual(dcp.get("section", "option"), "foo")
- mock_get.assert_called_with(dcp, "section", "option")
-
- mock_get.reset_mock()
- self.assertEqual(dcp.get("section", "option",
- default="bar", other="test"), "foo")
- mock_get.assert_called_with(dcp, "section", "option", other="test")
-
- for etype, err in [(ConfigParser.NoOptionError,
- ConfigParser.NoOptionError(None, None)),
- (ConfigParser.NoSectionError,
- ConfigParser.NoSectionError(None))]:
- mock_get.side_effect = err
- mock_get.reset_mock()
- self.assertEqual(dcp.get("section", "option", default="bar"), "bar")
- mock_get.assert_called_with(dcp, "section", "option")
-
- mock_get.reset_mock()
- self.assertRaises(etype, dcp.get, "section", "option")
- mock_get.assert_called_with(dcp, "section", "option")
-
- @patch("%s.ConfigParser.getboolean" % ConfigParser.__name__)
- def test_getboolean(self, mock_getboolean):
- dcp = DefaultConfigParser()
- mock_getboolean.return_value = True
- self.assertEqual(dcp.getboolean("section", "option"), True)
- mock_getboolean.assert_called_with(dcp, "section", "option")
-
- mock_getboolean.reset_mock()
- self.assertEqual(dcp.getboolean("section", "option",
- default=False, other="test"), True)
- mock_getboolean.assert_called_with(dcp, "section", "option",
- other="test")
-
- for etype, err in [(ConfigParser.NoOptionError,
- ConfigParser.NoOptionError(None, None)),
- (ConfigParser.NoSectionError,
- ConfigParser.NoSectionError(None))]:
- mock_getboolean.side_effect = err
- mock_getboolean.reset_mock()
- self.assertEqual(dcp.getboolean("section", "option", default=False),
- False)
- mock_getboolean.assert_called_with(dcp, "section", "option")
-
- mock_getboolean.reset_mock()
- self.assertRaises(etype, dcp.getboolean, "section", "option")
- mock_getboolean.assert_called_with(dcp, "section", "option")
-
-
-class TestOption(Bcfg2TestCase):
- def test__init(self):
- self.assertRaises(OptionFailure,
- Option,
- 'foo', False, cmd='f')
- self.assertRaises(OptionFailure,
- Option,
- 'foo', False, cmd='--f')
- self.assertRaises(OptionFailure,
- Option,
- 'foo', False, cmd='-foo')
- self.assertRaises(OptionFailure,
- Option,
- 'foo', False, cmd='-foo', long_arg=True)
- opt = Option('foo', False)
- self.assertTrue(opt.boolean)
- opt = Option('foo', False, odesc='<val>')
- self.assertFalse(opt.boolean)
- opt = Option('foo', False, cook=get_bool)
- self.assertFalse(opt.boolean)
- opt = Option('foo', "foo")
- self.assertFalse(opt.boolean)
-
- def test_get_cooked_value(self):
- opt = Option('foo', False)
- opt.boolean = True
- self.assertTrue(opt.get_cooked_value("anything"))
-
- opt = Option('foo', 'foo')
- opt.boolean = False
- opt.cook = False
- self.assertEqual("foo", opt.get_cooked_value("foo"))
-
- opt = Option('foo', 'foo')
- opt.boolean = False
- opt.cook = Mock()
- self.assertEqual(opt.cook.return_value, opt.get_cooked_value("foo"))
- opt.cook.assert_called_with("foo")
-
- def test_buildHelpMessage(self):
- opt = Option('foo', False)
- self.assertEqual(opt.buildHelpMessage(), '')
-
- opt = Option('foo', False, '-f')
- self.assertEqual(opt.buildHelpMessage().split(),
- ["-f", "foo"])
-
- opt = Option('foo', False, cmd="--foo", long_arg=True)
- self.assertEqual(opt.buildHelpMessage().split(),
- ["--foo", "foo"])
-
- opt = Option('foo', False, cmd="-f", odesc='<val>')
- self.assertEqual(opt.buildHelpMessage().split(),
- ["-f", "<val>", "foo"])
-
- opt = Option('foo', False, cmd="--foo", long_arg=True, odesc='<val>')
- self.assertEqual(opt.buildHelpMessage().split(),
- ["--foo=<val>", "foo"])
-
- def test_buildGetopt(self):
- opt = Option('foo', False)
- self.assertEqual(opt.buildGetopt(), '')
-
- opt = Option('foo', False, '-f')
- self.assertEqual(opt.buildGetopt(), "f")
-
- opt = Option('foo', False, cmd="--foo", long_arg=True)
- self.assertEqual(opt.buildGetopt(), '')
-
- opt = Option('foo', False, cmd="-f", odesc='<val>')
- self.assertEqual(opt.buildGetopt(), 'f:')
-
- opt = Option('foo', False, cmd="--foo", long_arg=True, odesc='<val>')
- self.assertEqual(opt.buildGetopt(), '')
-
- def test_buildLongGetopt(self):
- opt = Option('foo', False, cmd="--foo", long_arg=True)
- self.assertEqual(opt.buildLongGetopt(), 'foo')
-
- opt = Option('foo', False, cmd="--foo", long_arg=True, odesc='<val>')
- self.assertEqual(opt.buildLongGetopt(), 'foo=')
-
- def test_parse(self):
- cf = ('communication', 'password')
- o = Option('foo', default='test4', cmd='-F', env='TEST2',
- odesc='bar', cf=cf)
- o.parse([], ['-F', 'test'])
- self.assertEqual(o.value, 'test')
- o.parse([('-F', 'test2')], [])
- self.assertEqual(o.value, 'test2')
-
- os.environ['TEST2'] = 'test3'
- o.parse([], [])
- self.assertEqual(o.value, 'test3')
- del os.environ['TEST2']
-
- cfp = DefaultConfigParser()
- cfp.get = Mock()
- cfp.get.return_value = 'test5'
- o.parse([], [], configparser=cfp)
- cfp.get.assert_any_call(*cf)
- self.assertEqual(o.value, 'test5')
-
- o.cf = False
- o.parse([], [])
- assert o.value == 'test4'
-
-
-class TestOptionSet(Bcfg2TestCase):
- def test_buildGetopt(self):
- opts = [('foo', Option('foo', 'test1', cmd='-G')),
- ('bar', Option('foo', 'test2')),
- ('baz', Option('foo', 'test1', cmd='-H',
- odesc='1'))]
- oset = OptionSet(opts)
- res = oset.buildGetopt()
- self.assertIn('H:', res)
- self.assertIn('G', res)
- self.assertEqual(len(res), 3)
-
- def test_buildLongGetopt(self):
- opts = [('foo', Option('foo', 'test1', cmd='-G')),
- ('bar', Option('foo', 'test2')),
- ('baz', Option('foo', 'test1', cmd='--H',
- odesc='1', long_arg=True))]
- oset = OptionSet(opts)
- res = oset.buildLongGetopt()
- self.assertIn('H=', res)
- self.assertEqual(len(res), 1)
-
- def test_parse(self):
- opts = [('foo', Option('foo', 'test1', cmd='-G')),
- ('bar', Option('foo', 'test2')),
- ('baz', Option('foo', 'test1', cmd='-H',
- odesc='1'))]
- oset = OptionSet(opts)
- self.assertRaises(SystemExit,
- oset.parse,
- ['-G', '-H'])
- oset2 = OptionSet(opts)
- self.assertRaises(SystemExit,
- oset2.parse,
- ['-h'])
- oset3 = OptionSet(opts)
- oset3.parse(['-G'])
- self.assertTrue(oset3['foo'])
-
-
-class TestOptionParser(Bcfg2TestCase):
- def test__init(self):
- opts = [('foo', Option('foo', 'test1', cmd='-h')),
- ('bar', Option('foo', 'test2')),
- ('baz', Option('foo', 'test1', cmd='-H',
- odesc='1'))]
- oset1 = OptionParser(opts)
- self.assertEqual(oset1.cfile,
- DEFAULT_CONFIG_LOCATION)
- sys.argv = ['foo', '-C', '/usr/local/etc/bcfg2.conf']
- oset2 = OptionParser(opts)
- self.assertEqual(oset2.cfile,
- '/usr/local/etc/bcfg2.conf')
- sys.argv = []
- oset3 = OptionParser(opts)
- self.assertEqual(oset3.cfile,
- DEFAULT_CONFIG_LOCATION)
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestCache.py b/testsuite/Testsrc/Testlib/TestServer/TestCache.py
new file mode 100644
index 000000000..7c26e52b8
--- /dev/null
+++ b/testsuite/Testsrc/Testlib/TestServer/TestCache.py
@@ -0,0 +1,54 @@
+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)
+from common import *
+
+from Bcfg2.Server.Cache import *
+
+
+class TestCache(Bcfg2TestCase):
+ def test_cache(self):
+ md_cache = Cache("Metadata")
+ md_cache['foo.example.com'] = 'foo metadata'
+ md_cache['bar.example.com'] = 'bar metadata'
+ self.assertItemsEqual(list(iter(md_cache)),
+ ["foo.example.com", "bar.example.com"])
+
+ probe_cache = Cache("Probes", "data")
+ probe_cache['foo.example.com'] = 'foo probe data'
+ probe_cache['bar.example.com'] = 'bar probe data'
+ self.assertItemsEqual(list(iter(probe_cache)),
+ ["foo.example.com", "bar.example.com"])
+
+ md_cache.expire("foo.example.com")
+ self.assertItemsEqual(list(iter(md_cache)), ["bar.example.com"])
+ self.assertItemsEqual(list(iter(probe_cache)),
+ ["foo.example.com", "bar.example.com"])
+
+ probe_cache.expire("bar.example.com")
+ self.assertItemsEqual(list(iter(md_cache)), ["bar.example.com"])
+ self.assertItemsEqual(list(iter(probe_cache)),
+ ["foo.example.com"])
+
+ probe_cache['bar.example.com'] = 'bar probe data'
+ self.assertItemsEqual(list(iter(md_cache)), ["bar.example.com"])
+ self.assertItemsEqual(list(iter(probe_cache)),
+ ["foo.example.com", "bar.example.com"])
+
+ expire("bar.example.com")
+ self.assertEqual(len(md_cache), 0)
+ self.assertItemsEqual(list(iter(probe_cache)),
+ ["foo.example.com"])
+
+ probe_cache2 = Cache("Probes", "data")
+ self.assertItemsEqual(list(iter(probe_cache)),
+ list(iter(probe_cache2)))
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestEncryption.py b/testsuite/Testsrc/Testlib/TestServer/TestEncryption.py
new file mode 100644
index 000000000..cfb0c023b
--- /dev/null
+++ b/testsuite/Testsrc/Testlib/TestServer/TestEncryption.py
@@ -0,0 +1,149 @@
+# -*- coding: utf-8 -*-
+import os
+import sys
+from Bcfg2.Compat import b64decode
+from mock import Mock, MagicMock, patch
+
+# 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)
+from common import *
+
+try:
+ from Bcfg2.Server.Encryption import *
+ HAS_CRYPTO = True
+except ImportError:
+ HAS_CRYPTO = False
+
+
+class TestEncryption(Bcfg2TestCase):
+ plaintext = """foo bar
+baz
+\t\tquux
+""" + "a" * 16384 # 16K is completely arbitrary
+ iv = "0123456789ABCDEF"
+ salt = "01234567"
+ algo = "des_cbc"
+
+ @skipUnless(HAS_CRYPTO, "Encryption libraries not found")
+ def setUp(self):
+ Bcfg2.Options.setup.algorithm = "aes_256_cbc"
+
+ def test_str_crypt(self):
+ """ test str_encrypt/str_decrypt """
+ key = "a simple key"
+
+ # simple symmetrical test with no options
+ crypted = str_encrypt(self.plaintext, key)
+ self.assertEqual(self.plaintext, str_decrypt(crypted, key))
+
+ # symmetrical test with lots of options
+ crypted = str_encrypt(self.plaintext, key,
+ iv=self.iv, salt=self.salt,
+ algorithm=self.algo)
+ self.assertEqual(self.plaintext,
+ str_decrypt(crypted, key, iv=self.iv,
+ algorithm=self.algo))
+
+ # test that different algorithms are actually used
+ self.assertNotEqual(str_encrypt(self.plaintext, key),
+ str_encrypt(self.plaintext, key,
+ algorithm=self.algo))
+
+ # test that different keys are actually used
+ self.assertNotEqual(str_encrypt(self.plaintext, key),
+ str_encrypt(self.plaintext, "different key"))
+
+ # test that different IVs are actually used
+ self.assertNotEqual(str_encrypt(self.plaintext, key, iv=self.iv),
+ str_encrypt(self.plaintext, key))
+
+ # test that errors are raised on bad decrypts
+ crypted = str_encrypt(self.plaintext, key, algorithm=self.algo)
+ self.assertRaises(EVPError, str_decrypt,
+ crypted, "bogus key", algorithm=self.algo)
+ self.assertRaises(EVPError, str_decrypt,
+ crypted, key) # bogus algorithm
+
+ def test_ssl_crypt(self):
+ """ test ssl_encrypt/ssl_decrypt """
+ passwd = "a simple passphrase"
+
+ # simple symmetrical test
+ crypted = ssl_encrypt(self.plaintext, passwd)
+ self.assertEqual(self.plaintext, ssl_decrypt(crypted, passwd))
+
+ # more complex symmetrical test
+ crypted = ssl_encrypt(self.plaintext, passwd, algorithm=self.algo,
+ salt=self.salt)
+ self.assertEqual(self.plaintext,
+ ssl_decrypt(crypted, passwd, algorithm=self.algo))
+
+ # test that different algorithms are actually used
+ self.assertNotEqual(ssl_encrypt(self.plaintext, passwd),
+ ssl_encrypt(self.plaintext, passwd,
+ algorithm=self.algo))
+
+ # test that different passwords are actually used
+ self.assertNotEqual(ssl_encrypt(self.plaintext, passwd),
+ ssl_encrypt(self.plaintext, "different pass"))
+
+ # there's no reasonable test we can do to see if the
+ # output is base64-encoded, unfortunately, but if it's
+ # obviously not we fail
+ crypted = ssl_encrypt(self.plaintext, passwd)
+ self.assertRegexpMatches(crypted, r'^[A-Za-z0-9+/]+[=]{0,2}$')
+
+ # test that errors are raised on bad decrypts
+ crypted = ssl_encrypt(self.plaintext, passwd,
+ algorithm=self.algo)
+ self.assertRaises(EVPError, ssl_decrypt,
+ crypted, "bogus passwd", algorithm=self.algo)
+ self.assertRaises(EVPError, ssl_decrypt,
+ crypted, passwd) # bogus algorithm
+
+ def test_bruteforce_decrypt(self):
+ passwd = "a simple passphrase"
+ crypted = ssl_encrypt(self.plaintext, passwd)
+
+ # test with no passphrases given nor in config
+ Bcfg2.Options.setup.passphrases = dict()
+ self.assertRaises(EVPError,
+ bruteforce_decrypt, crypted)
+
+ # test with good passphrase given in function call
+ self.assertEqual(self.plaintext,
+ bruteforce_decrypt(crypted,
+ passphrases=["bogus pass",
+ passwd,
+ "also bogus"]))
+
+ # test with no good passphrase given nor in config
+ self.assertRaises(EVPError,
+ bruteforce_decrypt,
+ crypted, passphrases=["bogus", "also bogus"])
+
+ # test with good passphrase in config file
+ Bcfg2.Options.setup.passphrases = dict(bogus="bogus",
+ real=passwd,
+ bogus2="also bogus")
+ self.assertEqual(self.plaintext,
+ bruteforce_decrypt(crypted))
+
+ # test that passphrases given in function call take
+ # precedence over config
+ self.assertRaises(EVPError,
+ bruteforce_decrypt, crypted,
+ passphrases=["bogus", "also bogus"])
+
+ # test that different algorithms are used
+ crypted = ssl_encrypt(self.plaintext, passwd, algorithm=self.algo)
+ self.assertEqual(self.plaintext,
+ bruteforce_decrypt(crypted, algorithm=self.algo))
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testbase.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testbase.py
index 870983f60..f135a0197 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testbase.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testbase.py
@@ -1,6 +1,5 @@
import os
import sys
-import logging
from mock import Mock, MagicMock, patch
from Bcfg2.Server.Plugin.base import *
@@ -14,62 +13,23 @@ while path != '/':
break
path = os.path.dirname(path)
from common import *
-
-
-class TestDebuggable(Bcfg2TestCase):
- test_obj = Debuggable
-
- def get_obj(self):
- return self.test_obj()
-
- def test__init(self):
- d = self.get_obj()
- self.assertIsInstance(d.logger, logging.Logger)
- self.assertFalse(d.debug_flag)
-
- def test_set_debug(self):
- d = self.get_obj()
- self.assertEqual(True, d.set_debug(True))
- self.assertEqual(d.debug_flag, True)
-
- self.assertEqual(False, d.set_debug(False))
- self.assertEqual(d.debug_flag, False)
-
- def test_toggle_debug(self):
- d = self.get_obj()
- d.set_debug = Mock()
- orig = d.debug_flag
- self.assertEqual(d.toggle_debug(),
- d.set_debug.return_value)
- d.set_debug.assert_called_with(not orig)
-
- def test_debug_log(self):
- d = self.get_obj()
- d.logger = Mock()
- d.debug_flag = False
- d.debug_log("test")
- self.assertFalse(d.logger.error.called)
-
- d.logger.reset_mock()
- d.debug_log("test", flag=True)
- self.assertTrue(d.logger.error.called)
-
- d.logger.reset_mock()
- d.debug_flag = True
- d.debug_log("test")
- self.assertTrue(d.logger.error.called)
+from TestLogger import TestDebuggable
class TestPlugin(TestDebuggable):
test_obj = Plugin
+ def setUp(self):
+ TestDebuggable.setUp(self)
+ set_setup_default("filemonitor", MagicMock())
+
def get_obj(self, core=None):
if core is None:
core = Mock()
- core.setup = MagicMock()
+
@patchIf(not isinstance(os.makedirs, Mock), "os.makedirs", Mock())
def inner():
- return self.test_obj(core, datastore)
+ return self.test_obj(core)
return inner()
@patch("os.makedirs")
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testhelpers.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testhelpers.py
index ce17cb076..75bd4ec95 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testhelpers.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testhelpers.py
@@ -1,8 +1,10 @@
import os
import sys
import copy
+import genshi
import lxml.etree
import Bcfg2.Server
+import genshi.core
from Bcfg2.Compat import reduce
from mock import Mock, MagicMock, patch
from Bcfg2.Server.Plugin.helpers import *
@@ -21,6 +23,11 @@ from common import *
from TestServer.TestPlugin.Testbase import TestPlugin, TestDebuggable
from TestServer.TestPlugin.Testinterfaces import TestGenerator
+try:
+ from Bcfg2.Server.Encryption import EVPError
+except:
+ pass
+
def tostring(el):
return lxml.etree.tostring(el, xml_declaration=False).decode('UTF-8')
@@ -32,81 +39,41 @@ class FakeElementTree(lxml.etree._ElementTree):
class TestFunctions(Bcfg2TestCase):
- def test_bind_info(self):
- entry = lxml.etree.Element("Path", name="/test")
- metadata = Mock()
- default = dict(test1="test1", test2="test2")
- # test without infoxml
- bind_info(entry, metadata, default=default)
- self.assertItemsEqual(entry.attrib,
- dict(test1="test1",
- test2="test2",
- name="/test"))
-
- # test with bogus infoxml
- entry = lxml.etree.Element("Path", name="/test")
- infoxml = Mock()
- self.assertRaises(PluginExecutionError,
- bind_info,
- entry, metadata, infoxml=infoxml)
- infoxml.pnode.Match.assert_called_with(metadata, dict(), entry=entry)
-
- # test with valid infoxml
- entry = lxml.etree.Element("Path", name="/test")
- infoxml.reset_mock()
- infodata = {None: {"test3": "test3", "test4": "test4"}}
- def infoxml_rv(metadata, rv, entry=None):
- rv['Info'] = infodata
- infoxml.pnode.Match.side_effect = infoxml_rv
- bind_info(entry, metadata, infoxml=infoxml, default=default)
- # mock objects don't properly track the called-with value of
- # arguments whose value is changed by the function, so it
- # thinks Match() was called with the final value of the mdata
- # arg, not the initial value. makes this test a little less
- # worthwhile, TBH.
- infoxml.pnode.Match.assert_called_with(metadata, dict(Info=infodata),
- entry=entry)
- self.assertItemsEqual(entry.attrib,
- dict(test1="test1",
- test2="test2",
- test3="test3",
- test4="test4",
- name="/test"))
+ def test_removecomment(self):
+ data = [(None, "test", 1),
+ (None, "test2", 2)]
+ stream = [(genshi.core.COMMENT, "test", 0),
+ data[0],
+ (genshi.core.COMMENT, "test3", 0),
+ data[1]]
+ self.assertItemsEqual(list(removecomment(stream)), data)
class TestDatabaseBacked(TestPlugin):
test_obj = DatabaseBacked
- def get_obj(self, core=None):
- if not HAS_DJANGO:
- if core is None:
- core = MagicMock()
- # disable the database
- core.setup.cfp.getboolean.return_value = False
- return TestPlugin.get_obj(self, core=core)
+ def setUp(self):
+ TestPlugin.setUp(self)
+ set_setup_default("%s_db" % self.test_obj.__name__.lower(), False)
@skipUnless(HAS_DJANGO, "Django not found")
def test__use_db(self):
core = Mock()
- core.setup.cfp.getboolean.return_value = True
- db = self.get_obj(core)
+ db = self.get_obj(core=core)
+ attr = "%s_db" % self.test_obj.__name__.lower()
+
+ db.core.database_available = True
+ setattr(Bcfg2.Options.setup, attr, True)
self.assertTrue(db._use_db)
- core = Mock()
- core.setup.cfp.getboolean.return_value = False
- db = self.get_obj(core)
+ setattr(Bcfg2.Options.setup, attr, False)
self.assertFalse(db._use_db)
- Bcfg2.Server.Plugin.helpers.HAS_DJANGO = False
- core = Mock()
- core.setup.cfp.getboolean.return_value = False
- db = self.get_obj(core)
+ db.core.database_available = False
self.assertFalse(db._use_db)
- core = Mock()
- core.setup.cfp.getboolean.return_value = True
+ setattr(Bcfg2.Options.setup, attr, True)
self.assertRaises(PluginInitError, self.get_obj, core)
- Bcfg2.Server.Plugin.helpers.HAS_DJANGO = True
class TestPluginDatabaseModel(Bcfg2TestCase):
@@ -114,14 +81,18 @@ class TestPluginDatabaseModel(Bcfg2TestCase):
pass
-class TestFileBacked(Bcfg2TestCase):
+class TestFileBacked(TestDebuggable):
test_obj = FileBacked
path = os.path.join(datastore, "test")
- def get_obj(self, path=None, fam=None):
+ def setUp(self):
+ TestDebuggable.setUp(self)
+ set_setup_default("filemonitor", MagicMock())
+
+ def get_obj(self, path=None):
if path is None:
path = self.path
- return self.test_obj(path, fam=fam)
+ return self.test_obj(path)
@patch("%s.open" % builtins)
def test_HandleEvent(self, mock_open):
@@ -149,7 +120,7 @@ class TestFileBacked(Bcfg2TestCase):
self.assertFalse(fb.Index.called)
-class TestDirectoryBacked(Bcfg2TestCase):
+class TestDirectoryBacked(TestDebuggable):
test_obj = DirectoryBacked
testpaths = {1: '',
2: '/foo',
@@ -163,6 +134,10 @@ class TestDirectoryBacked(Bcfg2TestCase):
badevents = [] # DirectoryBacked handles all files, so there's no
# such thing as a bad event
+ def setUp(self):
+ TestDebuggable.setUp(self)
+ set_setup_default("filemonitor", MagicMock())
+
def test_child_interface(self):
""" ensure that the child object has the correct interface """
self.assertTrue(hasattr(self.test_obj.__child__, "HandleEvent"))
@@ -177,8 +152,7 @@ class TestDirectoryBacked(Bcfg2TestCase):
Mock())
def inner():
return self.test_obj(os.path.join(datastore,
- self.test_obj.__name__),
- fam)
+ self.test_obj.__name__))
return inner()
@patch("os.makedirs")
@@ -187,8 +161,8 @@ class TestDirectoryBacked(Bcfg2TestCase):
@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_exists.return_value = True
- db = self.test_obj(datastore, Mock())
mock_add_monitor.assert_called_with('')
mock_exists.assert_called_with(db.data)
self.assertFalse(mock_makedirs.called)
@@ -197,7 +171,7 @@ class TestDirectoryBacked(Bcfg2TestCase):
mock_exists.reset_mock()
mock_makedirs.reset_mock()
mock_exists.return_value = False
- db = self.test_obj(datastore, Mock())
+ db = self.test_obj(datastore)
mock_add_monitor.assert_called_with('')
mock_exists.assert_called_with(db.data)
mock_makedirs.assert_called_with(db.data)
@@ -268,10 +242,9 @@ class TestDirectoryBacked(Bcfg2TestCase):
db.fam = Mock()
class MockChild(Mock):
- def __init__(self, path, fam, **kwargs):
+ def __init__(self, path, **kwargs):
Mock.__init__(self, **kwargs)
self.path = path
- self.fam = fam
self.HandleEvent = Mock()
db.__child__ = MockChild
@@ -281,7 +254,6 @@ class TestDirectoryBacked(Bcfg2TestCase):
self.assertIn(path, db.entries)
self.assertEqual(db.entries[path].path,
os.path.join(db.data, path))
- self.assertEqual(db.entries[path].fam, db.fam)
db.entries[path].HandleEvent.assert_called_with(event)
@patch("os.path.isdir")
@@ -419,28 +391,31 @@ class TestXMLFileBacked(TestFileBacked):
should_monitor = None
path = os.path.join(datastore, "test", "test1.xml")
- def get_obj(self, path=None, fam=None, should_monitor=False):
+ def setUp(self):
+ TestFileBacked.setUp(self)
+ set_setup_default("encoding", 'utf-8')
+
+ def get_obj(self, path=None, should_monitor=False):
if path is None:
path = self.path
@patchIf(not isinstance(os.path.exists, Mock),
"os.path.exists", Mock())
def inner():
- return self.test_obj(path, fam=fam, should_monitor=should_monitor)
+ return self.test_obj(path, should_monitor=should_monitor)
return inner()
- def test__init(self):
- fam = Mock()
+ @patch("Bcfg2.Server.FileMonitor.get_fam")
+ def test__init(self, mock_get_fam):
xfb = self.get_obj()
+ self.assertEqual(xfb.fam, mock_get_fam.return_value)
+
if self.should_monitor:
- self.assertIsNotNone(xfb.fam)
- fam.reset_mock()
- xfb = self.get_obj(fam=fam, should_monitor=True)
- fam.AddMonitor.assert_called_with(self.path, xfb)
+ xfb = self.get_obj(should_monitor=True)
+ xfb.fam.AddMonitor.assert_called_with(self.path, xfb)
else:
- self.assertIsNone(xfb.fam)
- xfb = self.get_obj(fam=fam)
- self.assertFalse(fam.AddMonitor.called)
+ xfb = self.get_obj()
+ self.assertFalse(xfb.fam.AddMonitor.called)
@patch("glob.glob")
@patch("lxml.etree.parse")
@@ -609,6 +584,7 @@ class TestXMLFileBacked(TestFileBacked):
test3 = lxml.etree.Element("Test", name="test3")
replacements = {"/test/test2.xml": test2,
"/test/test_dir/test3.xml": test3}
+
def xinclude():
for el in xfb.xdata.findall('//%sinclude' %
Bcfg2.Server.XI_NAMESPACE):
@@ -626,23 +602,26 @@ class TestXMLFileBacked(TestFileBacked):
self.assertItemsEqual([tostring(e) for e in xfb.entries],
[tostring(e) for e in children])
+ @patch("Bcfg2.Server.FileMonitor.get_fam", Mock())
def test_add_monitor(self):
xfb = self.get_obj()
xfb.add_monitor("/test/test2.xml")
self.assertIn("/test/test2.xml", xfb.extra_monitors)
- fam = Mock()
- fam.reset_mock()
- xfb = self.get_obj(fam=fam)
- if xfb.fam:
- xfb.add_monitor("/test/test4.xml")
- fam.AddMonitor.assert_called_with("/test/test4.xml", xfb)
- self.assertIn("/test/test4.xml", xfb.extra_monitors)
+ xfb = self.get_obj()
+ xfb.fam = Mock()
+ xfb.add_monitor("/test/test4.xml")
+ xfb.fam.AddMonitor.assert_called_with("/test/test4.xml", xfb)
+ self.assertIn("/test/test4.xml", xfb.extra_monitors)
class TestStructFile(TestXMLFileBacked):
test_obj = StructFile
+ def setUp(self):
+ TestXMLFileBacked.setUp(self)
+ set_setup_default("lax_decryption", False)
+
def _get_test_data(self):
""" build a very complex set of test data """
# top-level group and client elements
@@ -684,7 +663,8 @@ class TestStructFile(TestXMLFileBacked):
lxml.etree.SubElement(groups[1], "Child", name="c3")
lxml.etree.SubElement(groups[1], "Child", name="c4")
- standalone.append(lxml.etree.SubElement(xdata, "Standalone", name="s1"))
+ standalone.append(lxml.etree.SubElement(xdata,
+ "Standalone", name="s1"))
groups[2] = lxml.etree.SubElement(xdata, "Client", name="client2",
include="false")
@@ -706,12 +686,140 @@ class TestStructFile(TestXMLFileBacked):
subchildren[3] = []
lxml.etree.SubElement(children[3][-1], "SubChild", name="subchild")
- standalone.append(lxml.etree.SubElement(xdata, "Standalone", name="s3"))
+ standalone.append(lxml.etree.SubElement(xdata,
+ "Standalone", name="s3"))
lxml.etree.SubElement(standalone[-1], "SubStandalone", name="sub1")
- children[4] = standalone
return (xdata, groups, subgroups, children, subchildren, standalone)
+ def _get_template_test_data(self):
+ (xdata, groups, subgroups, children, subchildren, standalone) = \
+ self._get_test_data()
+ template_xdata = \
+ lxml.etree.Element("Test", name="test",
+ nsmap=dict(py='http://genshi.edgewall.org/'))
+ template_xdata.extend(xdata.getchildren())
+ return (template_xdata, groups, subgroups, children, subchildren,
+ standalone)
+
+ @patch("genshi.template.TemplateLoader")
+ def test_Index(self, mock_TemplateLoader):
+ TestXMLFileBacked.test_Index(self)
+
+ sf = self.get_obj()
+ sf.encryption = False
+ sf.encoding = Mock()
+ (xdata, groups, subgroups, children, subchildren, standalone) = \
+ self._get_test_data()
+ sf.data = lxml.etree.tostring(xdata)
+
+ mock_TemplateLoader.reset_mock()
+ sf.Index()
+ self.assertFalse(mock_TemplateLoader.called)
+
+ mock_TemplateLoader.reset_mock()
+ template_xdata = \
+ lxml.etree.Element("Test", name="test",
+ nsmap=dict(py='http://genshi.edgewall.org/'))
+ template_xdata.extend(xdata.getchildren())
+ sf.data = lxml.etree.tostring(template_xdata)
+ sf.Index()
+ mock_TemplateLoader.assert_called_with()
+ loader = mock_TemplateLoader.return_value
+ loader.load.assert_called_with(sf.name,
+ cls=genshi.template.MarkupTemplate,
+ encoding=Bcfg2.Options.setup.encoding)
+ self.assertEqual(sf.template,
+ loader.load.return_value)
+
+ @skipUnless(HAS_CRYPTO, "No crypto libraries found, skipping")
+ def test_Index_crypto(self):
+ if not self.test_obj.encryption:
+ return
+ Bcfg2.Options.setup.lax_decryption = False
+ sf = self.get_obj()
+ sf._decrypt = Mock()
+ sf._decrypt.return_value = 'plaintext'
+ sf.data = '''
+<EncryptedData>
+ <Group name="test">
+ <Datum encrypted="foo">crypted</Datum>
+ </Group>
+ <Group name="test" negate="true">
+ <Datum>plain</Datum>
+ </Group>
+</EncryptedData>'''
+
+ # test successful decryption
+ sf.Index()
+ self.assertItemsEqual(
+ sf._decrypt.call_args_list,
+ [call(el) for el in sf.xdata.xpath("//*[@encrypted]")])
+ for el in sf.xdata.xpath("//*[@encrypted]"):
+ self.assertEqual(el.text, sf._decrypt.return_value)
+
+ # test failed decryption, strict
+ sf._decrypt.reset_mock()
+ sf._decrypt.side_effect = EVPError
+ self.assertRaises(PluginExecutionError, sf.Index)
+
+ # test failed decryption, lax
+ Bcfg2.Options.setup.lax_decryption = True
+ sf._decrypt.reset_mock()
+ sf.Index()
+ self.assertItemsEqual(
+ sf._decrypt.call_args_list,
+ [call(el) for el in sf.xdata.xpath("//*[@encrypted]")])
+
+ @skipUnless(HAS_CRYPTO, "No crypto libraries found, skipping")
+ @patchIf(HAS_CRYPTO, "Bcfg2.Server.Encryption.ssl_decrypt")
+ @patchIf(HAS_CRYPTO, "Bcfg2.Server.Encryption.bruteforce_decrypt")
+ def test_decrypt(self, mock_bruteforce, mock_ssl):
+ sf = self.get_obj()
+
+ def reset():
+ mock_bruteforce.reset_mock()
+ mock_ssl.reset_mock()
+
+
+ # test element without text contents
+ Bcfg2.Options.setup.passphrases = dict()
+ self.assertIsNone(sf._decrypt(lxml.etree.Element("Test")))
+ self.assertFalse(mock_bruteforce.called)
+ self.assertFalse(mock_ssl.called)
+
+ # test element with a passphrase in the config file
+ reset()
+ el = lxml.etree.Element("Test", encrypted="foo")
+ el.text = "crypted"
+ Bcfg2.Options.setup.passphrases = dict(foo="foopass", bar="barpass")
+ mock_ssl.return_value = "decrypted with ssl"
+ self.assertEqual(sf._decrypt(el), mock_ssl.return_value)
+ mock_ssl.assert_called_with(el.text, "foopass")
+ self.assertFalse(mock_bruteforce.called)
+
+ # test failure to decrypt element with a passphrase in the config
+ reset()
+ mock_ssl.side_effect = EVPError
+ self.assertRaises(EVPError, sf._decrypt, el)
+ mock_ssl.assert_called_with(el.text, "foopass")
+ self.assertFalse(mock_bruteforce.called)
+
+ # test element without valid passphrase
+ reset()
+ el.set("encrypted", "true")
+ mock_bruteforce.return_value = "decrypted with bruteforce"
+ self.assertEqual(sf._decrypt(el), mock_bruteforce.return_value)
+ mock_bruteforce.assert_called_with(el.text)
+ self.assertFalse(mock_ssl.called)
+
+ # test failure to decrypt element without valid passphrase
+ reset()
+ mock_bruteforce.side_effect = EVPError
+ self.assertRaises(EVPError, sf._decrypt, el)
+ mock_bruteforce.assert_called_with(el.text)
+ self.assertFalse(mock_ssl.called)
+
def test_include_element(self):
sf = self.get_obj()
metadata = Mock()
@@ -744,22 +852,63 @@ class TestStructFile(TestXMLFileBacked):
self.assertTrue(inc("Other"))
- @patch("Bcfg2.Server.Plugin.helpers.%s._include_element" %
- test_obj.__name__)
- def test__match(self, mock_include):
+ def test__match(self):
sf = self.get_obj()
+ sf._include_element = Mock()
metadata = Mock()
- (xdata, groups, subgroups, children, subchildren, standalone) = \
- self._get_test_data()
-
- mock_include.side_effect = \
- lambda x, _: (x.tag not in ['Client', 'Group'] or
+ sf._include_element.side_effect = \
+ lambda x, _: (x.tag not in sf._include_tests.keys() or
x.get("include") == "true")
- for i, group in groups.items():
- actual = sf._match(group, metadata)
- expected = children[i] + subchildren[i]
+ for test_data in [self._get_test_data(),
+ self._get_template_test_data()]:
+ (xdata, groups, subgroups, children, subchildren, standalone) = \
+ test_data
+
+ for i, group in groups.items():
+ actual = sf._match(group, metadata)
+ expected = children[i] + subchildren[i]
+ self.assertEqual(len(actual), len(expected))
+ # easiest way to compare the values is actually to make
+ # them into an XML document and let assertXMLEqual compare
+ # them
+ xactual = lxml.etree.Element("Container")
+ xactual.extend(actual)
+ xexpected = lxml.etree.Element("Container")
+ xexpected.extend(expected)
+ self.assertXMLEqual(xactual, xexpected)
+
+ for el in standalone:
+ self.assertXMLEqual(el, sf._match(el, metadata)[0])
+
+ def test_do_match(self):
+ Bcfg2.Options.setup.lax_decryption = True
+ sf = self.get_obj()
+ sf._match = Mock()
+
+ def match_rv(el, _):
+ if el.tag not in sf._include_tests.keys():
+ return [el]
+ elif el.get("include") == "true":
+ return el.getchildren()
+ else:
+ return []
+ sf._match.side_effect = match_rv
+
+ metadata = Mock()
+
+ for test_data in [self._get_test_data(),
+ self._get_template_test_data()]:
+ (xdata, groups, subgroups, children, subchildren, standalone) = \
+ test_data
+ sf.data = lxml.etree.tostring(xdata)
+ sf.Index()
+
+ actual = sf._do_match(metadata)
+ expected = reduce(lambda x, y: x + y,
+ list(children.values()) + \
+ list(subgroups.values())) + standalone
self.assertEqual(len(actual), len(expected))
# easiest way to compare the values is actually to make
# them into an XML document and let assertXMLEqual compare
@@ -770,428 +919,244 @@ class TestStructFile(TestXMLFileBacked):
xexpected.extend(expected)
self.assertXMLEqual(xactual, xexpected)
- for el in standalone:
- self.assertXMLEqual(el, sf._match(el, metadata)[0])
+ def test__xml_match(self):
+ sf = self.get_obj()
+ sf._include_element = Mock()
+ metadata = Mock()
+
+ sf._include_element.side_effect = \
+ lambda x, _: (x.tag not in sf._include_tests.keys() or
+ x.get("include") == "true")
- @patch("Bcfg2.Server.Plugin.helpers.%s._match" % test_obj.__name__)
- def test_Match(self, mock_match):
+ for test_data in [self._get_test_data(),
+ self._get_template_test_data()]:
+ (xdata, groups, subgroups, children, subchildren, standalone) = \
+ test_data
+
+ actual = copy.deepcopy(xdata)
+ for el in actual.getchildren():
+ sf._xml_match(el, metadata)
+ expected = lxml.etree.Element(xdata.tag, **dict(xdata.attrib))
+ expected.text = xdata.text
+ expected.extend(reduce(lambda x, y: x + y,
+ list(children.values()) + \
+ list(subchildren.values())))
+ expected.extend(standalone)
+ self.assertXMLEqual(actual, expected)
+
+ def test_do_xmlmatch(self):
sf = self.get_obj()
+ sf._xml_match = Mock()
metadata = Mock()
- (xdata, groups, subgroups, children, subchildren, standalone) = \
- self._get_test_data()
- sf.entries.extend(copy.deepcopy(xdata).getchildren())
+ for data_type, test_data in \
+ [("", self._get_test_data()),
+ ("templated ", self._get_template_test_data())]:
+ (xdata, groups, subgroups, children, subchildren, standalone) = \
+ test_data
+ sf.xdata = xdata
+ sf._xml_match.reset_mock()
+
+ sf._do_xmlmatch(metadata)
+ actual = []
+ for call in sf._xml_match.call_args_list:
+ actual.append(call[0][0])
+ self.assertEqual(call[0][1], metadata)
+ expected = list(groups.values()) + standalone
+ # easiest way to compare the values is actually to make
+ # them into an XML document and let assertXMLEqual compare
+ # them
+ xactual = lxml.etree.Element("Container")
+ xactual.extend(actual)
+ xexpected = lxml.etree.Element("Container")
+ xexpected.extend(expected)
+ self.assertXMLEqual(xactual, xexpected,
+ "XMLMatch() calls were incorrect for "
+ "%stest data" % data_type)
+
+ def test_match_ordering(self):
+ """ Match() returns elements in document order """
+ Bcfg2.Options.setup.lax_decryption = True
+ sf = self.get_obj()
+ sf._match = Mock()
def match_rv(el, _):
- if el.tag not in ['Client', 'Group']:
+ if el.tag not in sf._include_tests.keys():
return [el]
elif el.get("include") == "true":
return el.getchildren()
else:
return []
- mock_match.side_effect = match_rv
- actual = sf.Match(metadata)
- expected = reduce(lambda x, y: x + y,
- list(children.values()) + list(subgroups.values()))
- self.assertEqual(len(actual), len(expected))
- # easiest way to compare the values is actually to make
- # them into an XML document and let assertXMLEqual compare
- # them
- xactual = lxml.etree.Element("Container")
- xactual.extend(actual)
- xexpected = lxml.etree.Element("Container")
- xexpected.extend(expected)
- self.assertXMLEqual(xactual, xexpected)
-
- @patch("Bcfg2.Server.Plugin.helpers.%s._include_element" %
- test_obj.__name__)
- def test__xml_match(self, mock_include):
- sf = self.get_obj()
+ sf._match.side_effect = match_rv
+
metadata = Mock()
+ test_data = lxml.etree.Element("Test")
+ group = lxml.etree.SubElement(test_data, "Group", name="group",
+ include="true")
+ first = lxml.etree.SubElement(group, "Element", name="first")
+ second = lxml.etree.SubElement(test_data, "Element", name="second")
+
+ # sanity check to ensure that first and second are in the
+ # correct document order
+ if test_data.xpath("//Element") != [first, second]:
+ skip("lxml.etree does not construct documents in a reliable order")
+
+ sf.data = lxml.etree.tostring(test_data)
+ sf.Index()
+ rv = sf._do_match(metadata)
+ self.assertEqual(len(rv), 2,
+ "Match() seems to be broken, cannot test ordering")
+ msg = "Match() does not return elements in document order:\n" + \
+ "Expected: [%s, %s]\n" % (first, second) + \
+ "Actual: %s" % rv
+ self.assertXMLEqual(rv[0], first, msg)
+ self.assertXMLEqual(rv[1], second, msg)
+
+ # TODO: add tests to ensure that XMLMatch() returns elements
+ # in document order
+
+
+class TestInfoXML(TestStructFile):
+ test_obj = InfoXML
+
+ def _get_test_data(self):
(xdata, groups, subgroups, children, subchildren, standalone) = \
- self._get_test_data()
+ TestStructFile._get_test_data(self)
+ idx = max(groups.keys()) + 1
+ groups[idx] = lxml.etree.SubElement(
+ xdata, "Path", name="path1", include="true")
+ children[idx] = [lxml.etree.SubElement(groups[idx], "Child",
+ name="pc1")]
+ subgroups[idx] = [lxml.etree.SubElement(groups[idx], "Group",
+ name="pg1", include="true"),
+ lxml.etree.SubElement(groups[idx], "Client",
+ name="pc1", include="false")]
+ subchildren[idx] = [lxml.etree.SubElement(subgroups[idx][0],
+ "SubChild", name="sc1")]
+
+ idx += 1
+ groups[idx] = lxml.etree.SubElement(
+ xdata, "Path", name="path2", include="false")
+ children[idx] = []
+ subgroups[idx] = []
+ subchildren[idx] = []
+
+ path2 = lxml.etree.SubElement(groups[0], "Path", name="path2",
+ include="true")
+ subgroups[0].append(path2)
+ subchildren[0].append(lxml.etree.SubElement(path2, "SubChild",
+ name="sc2"))
+ return xdata, groups, subgroups, children, subchildren, standalone
- mock_include.side_effect = \
- lambda x, _: (x.tag not in ['Client', 'Group'] or
- x.get("include") == "true")
+ def test_include_element(self):
+ TestStructFile.test_include_element(self)
- actual = copy.deepcopy(xdata)
- for el in actual.getchildren():
- sf._xml_match(el, metadata)
- expected = lxml.etree.Element(xdata.tag, **dict(xdata.attrib))
- expected.text = xdata.text
- expected.extend(reduce(lambda x, y: x + y,
- list(children.values()) + list(subchildren.values())))
- expected.extend(standalone)
- self.assertXMLEqual(actual, expected)
-
- @patch("Bcfg2.Server.Plugin.helpers.%s._xml_match" % test_obj.__name__)
- def test_XMLMatch(self, mock_xml_match):
- sf = self.get_obj()
+ ix = self.get_obj()
metadata = Mock()
+ entry = lxml.etree.Element("Path", name="/etc/foo.conf")
+ inc = lambda tag, **attrs: \
+ ix._include_element(lxml.etree.Element(tag, **attrs),
+ metadata, entry)
+
+ self.assertFalse(inc("Path", name="/etc/bar.conf"))
+ self.assertFalse(inc("Path", name="/etc/foo.conf", negate="true"))
+ self.assertFalse(inc("Path", name="/etc/foo.conf", negate="tRuE"))
+ self.assertTrue(inc("Path", name="/etc/foo.conf"))
+ self.assertTrue(inc("Path", name="/etc/foo.conf", negate="false"))
+ self.assertTrue(inc("Path", name="/etc/foo.conf", negate="faLSe"))
+ self.assertTrue(inc("Path", name="/etc/bar.conf", negate="true"))
+ self.assertTrue(inc("Path", name="/etc/bar.conf", negate="tRUe"))
- (sf.xdata, groups, subgroups, children, subchildren, standalone) = \
- self._get_test_data()
-
- sf.XMLMatch(metadata)
- actual = []
- for call in mock_xml_match.call_args_list:
- actual.append(call[0][0])
- self.assertEqual(call[0][1], metadata)
- expected = list(groups.values()) + standalone
- # easiest way to compare the values is actually to make
- # them into an XML document and let assertXMLEqual compare
- # them
- xactual = lxml.etree.Element("Container")
- xactual.extend(actual)
- xexpected = lxml.etree.Element("Container")
- xexpected.extend(expected)
- self.assertXMLEqual(xactual, xexpected)
-
-
-class TestINode(Bcfg2TestCase):
- test_obj = INode
-
- # INode.__init__ and INode._load_children() call each other
- # recursively, which makes this class kind of a nightmare to test.
- # we have to first patch INode._load_children so that we can
- # create an INode object with no children loaded, then we unpatch
- # INode._load_children and patch INode.__init__ so that child
- # objects aren't actually created. but in order to test things
- # atomically, we do this umpteen times in order to test with
- # different data. this convenience method makes this a little
- # easier. fun fun fun.
- @patch("Bcfg2.Server.Plugin.helpers.%s._load_children" %
- test_obj.__name__, Mock())
- def _get_inode(self, data, idict):
- return self.test_obj(data, idict)
-
- def test_raw_predicates(self):
+ def test_BindEntry(self):
+ ix = self.get_obj()
+ entry = lxml.etree.Element("Path", name=self.path)
metadata = Mock()
- metadata.groups = ["group1", "group2"]
- metadata.hostname = "foo.example.com"
- entry = None
-
- parent_predicate = lambda m, e: True
- pred = eval(self.test_obj.raw['Client'] % dict(name="foo.example.com"),
- dict(predicate=parent_predicate))
- self.assertTrue(pred(metadata, entry))
- pred = eval(self.test_obj.raw['Client'] % dict(name="bar.example.com"),
- dict(predicate=parent_predicate))
- self.assertFalse(pred(metadata, entry))
-
- pred = eval(self.test_obj.raw['Group'] % dict(name="group1"),
- dict(predicate=parent_predicate))
- self.assertTrue(pred(metadata, entry))
- pred = eval(self.test_obj.raw['Group'] % dict(name="group3"),
- dict(predicate=parent_predicate))
- self.assertFalse(pred(metadata, entry))
-
- pred = eval(self.test_obj.nraw['Client'] % dict(name="foo.example.com"),
- dict(predicate=parent_predicate))
- self.assertFalse(pred(metadata, entry))
- pred = eval(self.test_obj.nraw['Client'] % dict(name="bar.example.com"),
- dict(predicate=parent_predicate))
- self.assertTrue(pred(metadata, entry))
-
- pred = eval(self.test_obj.nraw['Group'] % dict(name="group1"),
- dict(predicate=parent_predicate))
- self.assertFalse(pred(metadata, entry))
- pred = eval(self.test_obj.nraw['Group'] % dict(name="group3"),
- dict(predicate=parent_predicate))
- self.assertTrue(pred(metadata, entry))
-
- parent_predicate = lambda m, e: False
- pred = eval(self.test_obj.raw['Client'] % dict(name="foo.example.com"),
- dict(predicate=parent_predicate))
- self.assertFalse(pred(metadata, entry))
- pred = eval(self.test_obj.raw['Group'] % dict(name="group1"),
- dict(predicate=parent_predicate))
- self.assertFalse(pred(metadata, entry))
- pred = eval(self.test_obj.nraw['Client'] % dict(name="bar.example.com"),
- dict(predicate=parent_predicate))
- self.assertFalse(pred(metadata, entry))
- pred = eval(self.test_obj.nraw['Group'] % dict(name="group3"),
- dict(predicate=parent_predicate))
- self.assertFalse(pred(metadata, entry))
-
- self.assertItemsEqual(self.test_obj.containers,
- self.test_obj.raw.keys())
- self.assertItemsEqual(self.test_obj.containers,
- self.test_obj.nraw.keys())
-
- @patch("Bcfg2.Server.Plugin.helpers.INode._load_children")
- def test__init(self, mock_load_children):
- data = lxml.etree.Element("Bogus")
- # called with no parent, should not raise an exception; it's a
- # top-level tag in an XML file and so is not expected to be a
- # proper predicate
- INode(data, dict())
- self.assertRaises(PluginExecutionError,
- INode, data, dict(), Mock())
- data = lxml.etree.Element("Client", name="foo.example.com")
- idict = dict()
- inode = INode(data, idict)
- mock_load_children.assert_called_with(data, idict)
- self.assertTrue(inode.predicate(Mock(), Mock()))
+ # test with bogus infoxml
+ ix.Match = Mock()
+ ix.Match.return_value = []
+ self.assertRaises(PluginExecutionError,
+ ix.BindEntry, entry, metadata)
+ ix.Match.assert_called_with(metadata, entry)
- parent = Mock()
- parent.predicate = lambda m, e: True
- metadata = Mock()
- metadata.groups = ["group1", "group2"]
- metadata.hostname = "foo.example.com"
- entry = None
-
- # test setting predicate with parent object
- mock_load_children.reset_mock()
- inode = INode(data, idict, parent=parent)
- mock_load_children.assert_called_with(data, idict)
- self.assertTrue(inode.predicate(metadata, entry))
-
- # test negation
- data = lxml.etree.Element("Client", name="foo.example.com",
- negate="true")
- mock_load_children.reset_mock()
- inode = INode(data, idict, parent=parent)
- mock_load_children.assert_called_with(data, idict)
- self.assertFalse(inode.predicate(metadata, entry))
-
- # test failure of a matching predicate (client names do not match)
- data = lxml.etree.Element("Client", name="foo.example.com")
- metadata.hostname = "bar.example.com"
- mock_load_children.reset_mock()
- inode = INode(data, idict, parent=parent)
- mock_load_children.assert_called_with(data, idict)
- self.assertFalse(inode.predicate(metadata, entry))
-
- # test that parent predicate is AND'ed in correctly
- parent.predicate = lambda m, e: False
- metadata.hostname = "foo.example.com"
- mock_load_children.reset_mock()
- inode = INode(data, idict, parent=parent)
- mock_load_children.assert_called_with(data, idict)
- self.assertFalse(inode.predicate(metadata, entry))
-
- def test_load_children(self):
- data = lxml.etree.Element("Parent")
- child1 = lxml.etree.SubElement(data, "Client", name="foo.example.com")
- child2 = lxml.etree.SubElement(data, "Group", name="bar", negate="true")
- idict = dict()
-
- inode = self._get_inode(data, idict)
-
- @patch("Bcfg2.Server.Plugin.helpers.%s.__init__" %
- inode.__class__.__name__)
- def inner(mock_init):
- mock_init.return_value = None
- inode._load_children(data, idict)
- self.assertItemsEqual(mock_init.call_args_list,
- [call(child1, idict, inode),
- call(child2, idict, inode)])
- self.assertEqual(idict, dict())
- self.assertItemsEqual(inode.contents, dict())
+ # test with valid infoxml
+ ix.Match.reset_mock()
+ ix.Match.return_value = [lxml.etree.Element("Info",
+ mode="0600", owner="root")]
+ ix.BindEntry(entry, metadata)
+ ix.Match.assert_called_with(metadata, entry)
+ self.assertItemsEqual(entry.attrib,
+ dict(name=self.path, mode="0600", owner="root"))
- inner()
+ def _get_test_data(self):
+ (xdata, groups, subgroups, children, subchildren, standalone) = \
+ TestStructFile._get_test_data(self)
+ idx = max(groups.keys()) + 1
+ groups[idx] = lxml.etree.SubElement(
+ xdata, "Path", name="path1", include="true")
+ children[idx] = [lxml.etree.SubElement(groups[idx], "Child",
+ name="pc1")]
+ subgroups[idx] = [lxml.etree.SubElement(groups[idx], "Group",
+ name="pg1", include="true"),
+ lxml.etree.SubElement(groups[idx], "Client",
+ name="pc1", include="false")]
+ subchildren[idx] = [lxml.etree.SubElement(subgroups[idx][0],
+ "SubChild", name="sc1")]
+
+ idx += 1
+ groups[idx] = lxml.etree.SubElement(
+ xdata, "Path", name="path2", include="false")
+ children[idx] = []
+ subgroups[idx] = []
+ subchildren[idx] = []
+
+ path2 = lxml.etree.SubElement(groups[0], "Path", name="path2",
+ include="true")
+ subgroups[0].append(path2)
+ subchildren[0].append(lxml.etree.SubElement(path2, "SubChild",
+ name="sc2"))
+ return xdata, groups, subgroups, children, subchildren, standalone
- data = lxml.etree.Element("Parent")
- child1 = lxml.etree.SubElement(data, "Data", name="child1",
- attr="some attr")
- child1.text = "text"
- subchild1 = lxml.etree.SubElement(child1, "SubChild", name="subchild")
- child2 = lxml.etree.SubElement(data, "Group", name="bar", negate="true")
- idict = dict()
-
- inode = self._get_inode(data, idict)
- inode.ignore = []
-
- @patch("Bcfg2.Server.Plugin.helpers.%s.__init__" %
- inode.__class__.__name__)
- def inner2(mock_init):
- mock_init.return_value = None
- inode._load_children(data, idict)
- mock_init.assert_called_with(child2, idict, inode)
- tag = child1.tag
- name = child1.get("name")
- self.assertEqual(idict, dict(Data=[name]))
- self.assertIn(tag, inode.contents)
- self.assertIn(name, inode.contents[tag])
- self.assertItemsEqual(inode.contents[tag][name],
- dict(name=name,
- attr=child1.get('attr'),
- __text__=child1.text,
- __children__=[subchild1]))
-
- inner2()
-
- # test ignore. no ignore is set on INode by default, so we
- # have to set one
- old_ignore = copy.copy(self.test_obj.ignore)
- self.test_obj.ignore.append("Data")
- idict = dict()
-
- inode = self._get_inode(data, idict)
-
- @patch("Bcfg2.Server.Plugin.helpers.%s.__init__" %
- inode.__class__.__name__)
- def inner3(mock_init):
- mock_init.return_value = None
- inode._load_children(data, idict)
- mock_init.assert_called_with(child2, idict, inode)
- self.assertEqual(idict, dict())
- self.assertItemsEqual(inode.contents, dict())
-
- inner3()
- self.test_obj.ignore = old_ignore
-
- def test_Match(self):
- idata = lxml.etree.Element("Parent")
- contents = lxml.etree.SubElement(idata, "Data", name="contents",
- attr="some attr")
- child = lxml.etree.SubElement(idata, "Group", name="bar", negate="true")
-
- inode = INode(idata, dict())
- inode.predicate = Mock()
- inode.predicate.return_value = False
+ def test_include_element(self):
+ TestStructFile.test_include_element(self)
+ ix = self.get_obj()
metadata = Mock()
- metadata.groups = ['foo']
- data = dict()
- entry = child
-
- inode.Match(metadata, data, entry=child)
- self.assertEqual(data, dict())
- inode.predicate.assert_called_with(metadata, child)
-
- inode.predicate.reset_mock()
- inode.Match(metadata, data)
- self.assertEqual(data, dict())
- # can't easily compare XML args without the original
- # object, and we're testing that Match() works without an
- # XML object passed in, so...
- self.assertEqual(inode.predicate.call_args[0][0],
- metadata)
- self.assertXMLEqual(inode.predicate.call_args[0][1],
- lxml.etree.Element("None"))
-
- inode.predicate.reset_mock()
- inode.predicate.return_value = True
- inode.Match(metadata, data, entry=child)
- self.assertEqual(data, inode.contents)
- inode.predicate.assert_called_with(metadata, child)
-
-
-class TestInfoNode(TestINode):
- __test__ = True
- test_obj = InfoNode
-
- def test_raw_predicates(self):
- TestINode.test_raw_predicates(self)
- metadata = Mock()
- entry = lxml.etree.Element("Path", name="/tmp/foo",
- realname="/tmp/bar")
-
- parent_predicate = lambda m, d: True
- pred = eval(self.test_obj.raw['Path'] % dict(name="/tmp/foo"),
- dict(predicate=parent_predicate))
- self.assertTrue(pred(metadata, entry))
- pred = eval(InfoNode.raw['Path'] % dict(name="/tmp/bar"),
- dict(predicate=parent_predicate))
- self.assertTrue(pred(metadata, entry))
- pred = eval(InfoNode.raw['Path'] % dict(name="/tmp/bogus"),
- dict(predicate=parent_predicate))
- self.assertFalse(pred(metadata, entry))
-
- pred = eval(self.test_obj.nraw['Path'] % dict(name="/tmp/foo"),
- dict(predicate=parent_predicate))
- self.assertFalse(pred(metadata, entry))
- pred = eval(InfoNode.nraw['Path'] % dict(name="/tmp/bar"),
- dict(predicate=parent_predicate))
- self.assertFalse(pred(metadata, entry))
- pred = eval(InfoNode.nraw['Path'] % dict(name="/tmp/bogus"),
- dict(predicate=parent_predicate))
- self.assertTrue(pred(metadata, entry))
-
- parent_predicate = lambda m, d: False
- pred = eval(self.test_obj.raw['Path'] % dict(name="/tmp/foo"),
- dict(predicate=parent_predicate))
- self.assertFalse(pred(metadata, entry))
- pred = eval(InfoNode.raw['Path'] % dict(name="/tmp/bar"),
- dict(predicate=parent_predicate))
- self.assertFalse(pred(metadata, entry))
- pred = eval(InfoNode.nraw['Path'] % dict(name="/tmp/bogus"),
- dict(predicate=parent_predicate))
- self.assertFalse(pred(metadata, entry))
-
-
-class TestXMLSrc(TestXMLFileBacked):
- test_obj = XMLSrc
-
- def test_node_interface(self):
- # ensure that the node object has the necessary interface
- self.assertTrue(hasattr(self.test_obj.__node__, "Match"))
+ entry = lxml.etree.Element("Path", name="/etc/foo.conf")
+ inc = lambda tag, **attrs: \
+ ix._include_element(lxml.etree.Element(tag, **attrs),
+ metadata, entry)
+
+ self.assertFalse(inc("Path", name="/etc/bar.conf"))
+ self.assertFalse(inc("Path", name="/etc/foo.conf", negate="true"))
+ self.assertFalse(inc("Path", name="/etc/foo.conf", negate="tRuE"))
+ self.assertTrue(inc("Path", name="/etc/foo.conf"))
+ self.assertTrue(inc("Path", name="/etc/foo.conf", negate="false"))
+ self.assertTrue(inc("Path", name="/etc/foo.conf", negate="faLSe"))
+ self.assertTrue(inc("Path", name="/etc/bar.conf", negate="true"))
+ self.assertTrue(inc("Path", name="/etc/bar.conf", negate="tRUe"))
- @patch("lxml.etree.parse")
- def test_HandleEvent(self, mock_parse):
- xdata = lxml.etree.Element("Test")
- lxml.etree.SubElement(xdata, "Path", name="path", attr="whatever")
-
- xsrc = self.get_obj("/test/foo.xml")
- xsrc.__node__ = Mock()
- mock_parse.return_value = xdata.getroottree()
-
- if xsrc.__priority_required__:
- # test with no priority at all
- self.assertRaises(PluginExecutionError,
- xsrc.HandleEvent, Mock())
-
- # test with bogus priority
- xdata.set("priority", "cow")
- mock_parse.return_value = xdata.getroottree()
- self.assertRaises(PluginExecutionError,
- xsrc.HandleEvent, Mock())
-
- # assign a priority to use in future tests
- xdata.set("priority", "10")
- mock_parse.return_value = xdata.getroottree()
-
- mock_parse.reset_mock()
- xsrc = self.get_obj("/test/foo.xml")
- xsrc.__node__ = Mock()
- xsrc.HandleEvent(Mock())
- mock_parse.assert_called_with("/test/foo.xml",
- parser=Bcfg2.Server.XMLParser)
- self.assertXMLEqual(xsrc.__node__.call_args[0][0], xdata)
- self.assertEqual(xsrc.__node__.call_args[0][1], dict())
- self.assertEqual(xsrc.pnode, xsrc.__node__.return_value)
- self.assertEqual(xsrc.cache, None)
-
- @patch("Bcfg2.Server.Plugin.helpers.XMLSrc.HandleEvent")
- def test_Cache(self, mock_HandleEvent):
- xsrc = self.get_obj("/test/foo.xml")
+ def test_BindEntry(self):
+ ix = self.get_obj()
+ entry = lxml.etree.Element("Path", name=self.path)
metadata = Mock()
- xsrc.Cache(metadata)
- mock_HandleEvent.assert_any_call()
-
- xsrc.pnode = Mock()
- xsrc.Cache(metadata)
- xsrc.pnode.Match.assert_called_with(metadata, xsrc.__cacheobj__())
- self.assertEqual(xsrc.cache[0], metadata)
-
- xsrc.pnode.reset_mock()
- xsrc.Cache(metadata)
- self.assertFalse(xsrc.pnode.Mock.called)
- self.assertEqual(xsrc.cache[0], metadata)
-
- xsrc.cache = ("bogus")
- xsrc.Cache(metadata)
- xsrc.pnode.Match.assert_called_with(metadata, xsrc.__cacheobj__())
- self.assertEqual(xsrc.cache[0], metadata)
+ # test with bogus infoxml
+ ix.Match = Mock()
+ ix.Match.return_value = []
+ self.assertRaises(PluginExecutionError,
+ ix.BindEntry, entry, metadata)
+ ix.Match.assert_called_with(metadata, entry)
-class TestInfoXML(TestXMLSrc):
- test_obj = InfoXML
+ # test with valid infoxml
+ ix.Match.reset_mock()
+ ix.Match.return_value = [lxml.etree.Element("Info",
+ mode="0600", owner="root")]
+ ix.BindEntry(entry, metadata)
+ ix.Match.assert_called_with(metadata, entry)
+ self.assertItemsEqual(entry.attrib,
+ dict(name=self.path, mode="0600", owner="root"))
class TestXMLDirectoryBacked(TestDirectoryBacked):
@@ -1203,6 +1168,11 @@ class TestXMLDirectoryBacked(TestDirectoryBacked):
class TestPrioDir(TestPlugin, TestGenerator, TestXMLDirectoryBacked):
test_obj = PrioDir
+ def setUp(self):
+ TestPlugin.setUp(self)
+ TestGenerator.setUp(self)
+ TestXMLDirectoryBacked.setUp(self)
+
def get_obj(self, core=None):
if core is None:
core = Mock()
@@ -1212,7 +1182,7 @@ class TestPrioDir(TestPlugin, TestGenerator, TestXMLDirectoryBacked):
Mock())
@patchIf(not isinstance(os.makedirs, Mock), "os.makedirs", Mock())
def inner():
- return self.test_obj(core, datastore)
+ return self.test_obj(core)
return inner()
@@ -1223,13 +1193,20 @@ class TestPrioDir(TestPlugin, TestGenerator, TestXMLDirectoryBacked):
Mock())
def inner():
pd = self.get_obj()
- test1 = Mock()
- test1.items = dict(Path=["/etc/foo.conf", "/etc/bar.conf"])
- test2 = Mock()
- test2.items = dict(Path=["/etc/baz.conf"],
- Package=["quux", "xyzzy"])
- pd.entries = {"/test1.xml": test1,
- "/test2.xml": test2}
+ test1 = lxml.etree.Element("Test")
+ lxml.etree.SubElement(test1, "Path", name="/etc/foo.conf")
+ lxml.etree.SubElement(lxml.etree.SubElement(test1,
+ "Group", name="foo"),
+ "Path", name="/etc/bar.conf")
+
+ test2 = lxml.etree.Element("Test")
+ lxml.etree.SubElement(test2, "Path", name="/etc/baz.conf")
+ lxml.etree.SubElement(test2, "Package", name="quux")
+ lxml.etree.SubElement(lxml.etree.SubElement(test2,
+ "Group", name="bar"),
+ "Package", name="xyzzy")
+ pd.entries = {"/test1.xml": Mock(xdata=test1),
+ "/test2.xml": Mock(xdata=test2)}
pd.HandleEvent(Mock())
self.assertItemsEqual(pd.Entries,
dict(Path={"/etc/foo.conf": pd.BindEntry,
@@ -1242,32 +1219,17 @@ class TestPrioDir(TestPlugin, TestGenerator, TestXMLDirectoryBacked):
def test__matches(self):
pd = self.get_obj()
- self.assertTrue(pd._matches(lxml.etree.Element("Test",
- name="/etc/foo.conf"),
- Mock(),
- {"/etc/foo.conf": pd.BindEntry,
- "/etc/bar.conf": pd.BindEntry}))
- self.assertFalse(pd._matches(lxml.etree.Element("Test",
- name="/etc/baz.conf"),
- Mock(),
- {"/etc/foo.conf": pd.BindEntry,
- "/etc/bar.conf": pd.BindEntry}))
+ entry = lxml.etree.Element("Test", name="/etc/foo.conf")
+ self.assertTrue(pd._matches(entry, Mock(),
+ lxml.etree.Element("Test",
+ name="/etc/foo.conf")))
+ self.assertFalse(pd._matches(entry, Mock(),
+ lxml.etree.Element("Test",
+ name="/etc/baz.conf")))
def test_BindEntry(self):
pd = self.get_obj()
- pd.get_attrs = Mock(return_value=dict(test1="test1", test2="test2"))
- entry = lxml.etree.Element("Path", name="/etc/foo.conf", test1="bogus")
- metadata = Mock()
- pd.BindEntry(entry, metadata)
- pd.get_attrs.assert_called_with(entry, metadata)
- self.assertItemsEqual(entry.attrib,
- dict(name="/etc/foo.conf",
- test1="test1", test2="test2"))
-
- def test_get_attrs(self):
- pd = self.get_obj()
- entry = lxml.etree.Element("Path", name="/etc/foo.conf")
- children = [lxml.etree.Element("Child")]
+ children = [lxml.etree.Element("Child", name="child")]
metadata = Mock()
pd.entries = dict()
@@ -1275,58 +1237,59 @@ class TestPrioDir(TestPlugin, TestGenerator, TestXMLDirectoryBacked):
metadata.reset_mock()
for src in pd.entries.values():
src.reset_mock()
- src.cache = None
# test with no matches
- self.assertRaises(PluginExecutionError,
- pd.get_attrs, entry, metadata)
+ self.assertRaises(PluginExecutionError, pd.BindEntry, Mock(), metadata)
- def add_entry(name, data, prio=10):
+ def add_entry(name, data):
path = os.path.join(pd.data, name)
pd.entries[path] = Mock()
- pd.entries[path].priority = prio
- def do_Cache(metadata):
- pd.entries[path].cache = (metadata, data)
- pd.entries[path].Cache.side_effect = do_Cache
-
- add_entry('test1.xml',
- dict(Path={'/etc/foo.conf': dict(attr="attr1",
- __children__=children),
- '/etc/bar.conf': dict()}))
- add_entry('test2.xml',
- dict(Path={'/etc/bar.conf': dict(__text__="text",
- attr="attr1")},
- Package={'quux': dict(),
- 'xyzzy': dict()}),
- prio=20)
- add_entry('test3.xml',
- dict(Path={'/etc/baz.conf': dict()},
- Package={'xyzzy': dict()}),
- prio=20)
-
- # test with exactly one match, __children__
+ pd.entries[path].priority = data.get("priority")
+ pd.entries[path].XMLMatch.return_value = data
+
+ test1 = lxml.etree.Element("Rules", priority="10")
+ path1 = lxml.etree.SubElement(test1, "Path", name="/etc/foo.conf",
+ attr="attr1")
+ path1.extend(children)
+ lxml.etree.SubElement(test1, "Path", name="/etc/bar.conf")
+ add_entry('test1.xml', test1)
+
+ test2 = lxml.etree.Element("Rules", priority="20")
+ path2 = lxml.etree.SubElement(test2, "Path", name="/etc/bar.conf",
+ attr="attr1")
+ path2.text = "text"
+ lxml.etree.SubElement(test2, "Package", name="quux")
+ lxml.etree.SubElement(test2, "Package", name="xyzzy")
+ add_entry('test2.xml', test2)
+
+ test3 = lxml.etree.Element("Rules", priority="20")
+ lxml.etree.SubElement(test3, "Path", name="/etc/baz.conf")
+ lxml.etree.SubElement(test3, "Package", name="xyzzy")
+ add_entry('test3.xml', test3)
+
+ # test with exactly one match, children
reset()
- self.assertItemsEqual(pd.get_attrs(entry, metadata),
- dict(attr="attr1"))
+ entry = lxml.etree.Element("Path", name="/etc/foo.conf")
+ pd.BindEntry(entry, metadata)
+ self.assertXMLEqual(entry, path1)
+ self.assertIsNot(entry, path1)
for src in pd.entries.values():
- src.Cache.assert_called_with(metadata)
- self.assertEqual(len(entry.getchildren()), 1)
- self.assertXMLEqual(entry.getchildren()[0], children[0])
+ src.XMLMatch.assert_called_with(metadata)
- # test with multiple matches with different priorities, __text__
+ # test with multiple matches with different priorities, text
reset()
entry = lxml.etree.Element("Path", name="/etc/bar.conf")
- self.assertItemsEqual(pd.get_attrs(entry, metadata),
- dict(attr="attr1"))
+ pd.BindEntry(entry, metadata)
+ self.assertXMLEqual(entry, path2)
+ self.assertIsNot(entry, path2)
for src in pd.entries.values():
- src.Cache.assert_called_with(metadata)
- self.assertEqual(entry.text, "text")
+ src.XMLMatch.assert_called_with(metadata)
# test with multiple matches with identical priorities
reset()
entry = lxml.etree.Element("Package", name="xyzzy")
self.assertRaises(PluginExecutionError,
- pd.get_attrs, entry, metadata)
+ pd.BindEntry, entry, metadata)
class TestSpecificity(Bcfg2TestCase):
@@ -1390,16 +1353,20 @@ class TestSpecificity(Bcfg2TestCase):
self.assertGreaterEqual(specs[j], specs[i])
-class TestSpecificData(Bcfg2TestCase):
+class TestSpecificData(TestDebuggable):
test_obj = SpecificData
path = os.path.join(datastore, "test.txt")
- def get_obj(self, name=None, specific=None, encoding=None):
+ def setUp(self):
+ TestDebuggable.setUp(self)
+ set_setup_default("encoding", "utf-8")
+
+ def get_obj(self, name=None, specific=None):
if name is None:
name = self.path
if specific is None:
specific = Mock()
- return self.test_obj(name, specific, encoding)
+ return self.test_obj(name, specific)
def test__init(self):
pass
@@ -1411,10 +1378,10 @@ class TestSpecificData(Bcfg2TestCase):
sd = self.get_obj()
sd.handle_event(event)
self.assertFalse(mock_open.called)
- if hasattr(sd, 'data'):
- self.assertIsNone(sd.data)
- else:
+ try:
self.assertFalse(hasattr(sd, 'data'))
+ except AssertionError:
+ self.assertIsNone(sd.data)
event = Mock()
mock_open.return_value.read.return_value = "test"
@@ -1441,9 +1408,18 @@ class TestEntrySet(TestDebuggable):
ignore = ["foo~", ".#foo", ".foo.swp", ".foo.swx",
"test.txt.genshi_include", "test.G_foo.genshi_include"]
- def get_obj(self, basename="test", path=datastore, entry_type=MagicMock(),
- encoding=None):
- return self.test_obj(basename, path, entry_type, encoding)
+ def setUp(self):
+ TestDebuggable.setUp(self)
+ set_setup_default("default_owner")
+ set_setup_default("default_group")
+ set_setup_default("default_mode")
+ set_setup_default("default_secontext")
+ set_setup_default("default_important", False)
+ set_setup_default("default_paranoid", False)
+ set_setup_default("default_sensitive", False)
+
+ def get_obj(self, basename="test", entry_type=MagicMock()):
+ return self.test_obj(basename, path, entry_type)
def test__init(self):
for basename in self.basenames:
@@ -1573,25 +1549,25 @@ class TestEntrySet(TestDebuggable):
eset.reset_metadata.reset_mock()
eset.entry_init.reset_mock()
- for fname in ["info", "info.xml", ":info"]:
- for evt in ["exists", "created", "changed"]:
- reset()
- event = Mock()
- event.code2str.return_value = evt
- event.filename = fname
- eset.handle_event(event)
- eset.update_metadata.assert_called_with(event)
- self.assertFalse(eset.entry_init.called)
- self.assertFalse(eset.reset_metadata.called)
-
+ fname = "info.xml"
+ for evt in ["exists", "created", "changed"]:
reset()
event = Mock()
- event.code2str.return_value = "deleted"
+ event.code2str.return_value = evt
event.filename = fname
eset.handle_event(event)
- eset.reset_metadata.assert_called_with(event)
+ eset.update_metadata.assert_called_with(event)
self.assertFalse(eset.entry_init.called)
- self.assertFalse(eset.update_metadata.called)
+ self.assertFalse(eset.reset_metadata.called)
+
+ reset()
+ event = Mock()
+ event.code2str.return_value = "deleted"
+ event.filename = fname
+ eset.handle_event(event)
+ eset.reset_metadata.assert_called_with(event)
+ self.assertFalse(eset.entry_init.called)
+ self.assertFalse(eset.update_metadata.called)
for evt in ["exists", "created", "changed"]:
reset()
@@ -1638,8 +1614,9 @@ class TestEntrySet(TestDebuggable):
eset.entry_init(event)
eset.specificity_from_filename.assert_called_with("test.txt",
specific=None)
- eset.entry_type.assert_called_with(os.path.join(eset.path, "test.txt"),
- eset.specificity_from_filename.return_value, None)
+ eset.entry_type.assert_called_with(
+ os.path.join(eset.path, "test.txt"),
+ eset.specificity_from_filename.return_value)
eset.entry_type.return_value.handle_event.assert_called_with(event)
self.assertIn("test.txt", eset.entries)
@@ -1660,8 +1637,7 @@ class TestEntrySet(TestDebuggable):
eset.specificity_from_filename.assert_called_with("test2.txt",
specific=specific)
etype.assert_called_with(os.path.join(eset.path, "test2.txt"),
- eset.specificity_from_filename.return_value,
- None)
+ eset.specificity_from_filename.return_value)
etype.return_value.handle_event.assert_called_with(event)
self.assertIn("test2.txt", eset.entries)
@@ -1750,26 +1726,8 @@ class TestEntrySet(TestDebuggable):
self.assertFalse(mock_InfoXML.called)
eset.infoxml.HandleEvent.assert_called_with(event)
- for fname in [':info', 'info']:
- event = Mock()
- event.filename = fname
-
- idata = ["owner:owner",
- "group: GROUP",
- "mode: 775",
- "important: true",
- "bogus: line"]
- mock_open.return_value.readlines.return_value = idata
- eset.update_metadata(event)
- expected = DEFAULT_FILE_METADATA.copy()
- expected['owner'] = 'owner'
- expected['group'] = 'GROUP'
- expected['mode'] = '0775'
- expected['important'] = 'true'
- self.assertItemsEqual(eset.metadata,
- expected)
-
- def test_reset_metadata(self):
+ @patch("Bcfg2.Server.Plugin.helpers.default_path_metadata")
+ def test_reset_metadata(self, mock_default_path_metadata):
eset = self.get_obj()
# test info.xml
@@ -1779,29 +1737,22 @@ class TestEntrySet(TestDebuggable):
eset.reset_metadata(event)
self.assertIsNone(eset.infoxml)
- for fname in [':info', 'info']:
- event = Mock()
- event.filename = fname
- eset.metadata = Mock()
- eset.reset_metadata(event)
- self.assertItemsEqual(eset.metadata, DEFAULT_FILE_METADATA)
-
- @patch("Bcfg2.Server.Plugin.helpers.bind_info")
- def test_bind_info_to_entry(self, mock_bind_info):
- # There's a strange scoping issue in py3k that prevents this
- # test from working as expected on sub-classes of EntrySet.
- # No idea what's going on, but until I can figure it out we
- # skip this test on subclasses
- if inPy3k and self.test_obj != EntrySet:
- return skip("Skipping this test for py3k scoping issues")
-
+ def test_bind_info_to_entry(self):
eset = self.get_obj()
- entry = Mock()
+ eset.metadata = dict(owner="root", group="root")
+ entry = lxml.etree.Element("Path", name="/test")
metadata = Mock()
+ eset.infoxml = None
+ eset.bind_info_to_entry(entry, metadata)
+ self.assertItemsEqual(entry.attrib,
+ dict(name="/test", owner="root", group="root"))
+
+ entry = lxml.etree.Element("Path", name="/test")
+ eset.infoxml = Mock()
eset.bind_info_to_entry(entry, metadata)
- mock_bind_info.assert_called_with(entry, metadata,
- infoxml=eset.infoxml,
- default=eset.metadata)
+ self.assertItemsEqual(entry.attrib,
+ dict(name="/test", owner="root", group="root"))
+ eset.infoxml.BindEntry.assert_called_with(entry, metadata)
def test_bind_entry(self):
eset = self.get_obj()
@@ -1820,15 +1771,14 @@ class TestEntrySet(TestDebuggable):
class TestGroupSpool(TestPlugin, TestGenerator):
test_obj = GroupSpool
+ def setUp(self):
+ TestPlugin.setUp(self)
+ TestGenerator.setUp(self)
+ set_setup_default("encoding", "utf-8")
+
def get_obj(self, core=None):
if core is None:
core = MagicMock()
- core.setup = MagicMock()
- else:
- try:
- core.setup['encoding']
- except TypeError:
- core.setup.__getitem__ = MagicMock()
@patch("%s.%s.AddDirectoryMonitor" % (self.test_obj.__module__,
self.test_obj.__name__),
@@ -1843,7 +1793,7 @@ class TestGroupSpool(TestPlugin, TestGenerator):
@patch("%s.%s.AddDirectoryMonitor" % (self.test_obj.__module__,
self.test_obj.__name__))
def inner(mock_Add):
- gs = self.test_obj(MagicMock(), datastore)
+ gs = self.test_obj(MagicMock())
mock_Add.assert_called_with('')
self.assertItemsEqual(gs.Entries, {gs.entry_type: {}})
@@ -1899,8 +1849,7 @@ class TestGroupSpool(TestPlugin, TestGenerator):
self.assertFalse(gs.AddDirectoryMonitor.called)
gs.es_cls.assert_called_with(gs.filename_pattern,
gs.data + ident,
- gs.es_child_cls,
- gs.encoding)
+ gs.es_child_cls)
self.assertIn(ident, gs.entries)
self.assertEqual(gs.entries[ident], gs.es_cls.return_value)
self.assertIn(ident, gs.Entries[gs.entry_type])
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testinterfaces.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testinterfaces.py
index 1f5c4790b..bbfb495c4 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testinterfaces.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testinterfaces.py
@@ -357,3 +357,20 @@ class TestVersion(TestPlugin):
class TestClientRunHooks(Bcfg2TestCase):
""" placeholder for future tests """
pass
+
+
+class TestClientACLs(Bcfg2TestCase):
+ test_obj = ClientACLs
+
+ def get_obj(self):
+ return self.test_obj()
+
+ def test_check_acl_ip(self):
+ ca = self.get_obj()
+ self.assertIn(ca.check_acl_ip(Mock(), Mock()),
+ [True, False, None])
+
+ def test_check_acl_metadata(self):
+ ca = self.get_obj()
+ self.assertIn(ca.check_acl_metadata(Mock(), Mock()),
+ [True, False])
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestACL.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestACL.py
new file mode 100644
index 000000000..86a960701
--- /dev/null
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestACL.py
@@ -0,0 +1,223 @@
+import os
+import sys
+import lxml.etree
+import Bcfg2.Server.Plugin
+from mock import Mock, MagicMock, patch
+from Bcfg2.Server.Plugins.ACL import *
+
+# 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)
+from common import *
+from TestPlugin import TestXMLFileBacked, TestStructFile, TestPlugin, \
+ TestClientACLs
+
+
+class TestFunctions(Bcfg2TestCase):
+ def test_rmi_names_equal(self):
+ good_cases = [('*', 'foo'),
+ ('foo', 'foo'),
+ ('foo.*', 'foo.bar'),
+ ('*.*', 'foo.bar'),
+ ('foo.bar', 'foo.bar'),
+ ('*.bar', 'foo.bar'),
+ ('foo.*.bar', 'foo.baz.bar')]
+ bad_cases = [('foo', 'bar'),
+ ('*', 'foo.bar'),
+ ('*.*', 'foo'),
+ ('*.*', 'foo.bar.baz'),
+ ('foo.*', 'bar.foo'),
+ ('*.bar', 'bar.foo'),
+ ('foo.*', 'foobar')]
+ for first, second in good_cases:
+ self.assertTrue(rmi_names_equal(first, second),
+ "rmi_names_equal(%s, %s) unexpectedly False" %
+ (first, second))
+ self.assertTrue(rmi_names_equal(second, first),
+ "rmi_names_equal(%s, %s) unexpectedly False" %
+ (second, first))
+ for first, second in bad_cases:
+ self.assertFalse(rmi_names_equal(first, second),
+ "rmi_names_equal(%s, %s) unexpectedly True" %
+ (first, second))
+ self.assertFalse(rmi_names_equal(second, first),
+ "rmi_names_equal(%s, %s) unexpectedly True" %
+ (second, first))
+
+ def test_ip_matches(self):
+ good_cases = [
+ ("192.168.1.1", lxml.etree.Element("test", address="192.168.1.1")),
+ ("192.168.1.17", lxml.etree.Element("test", address="192.168.1.0",
+ netmask="24")),
+ ("192.168.1.17", lxml.etree.Element("test", address="192.168.1.0",
+ netmask="255.255.255.0")),
+ ("192.168.1.31", lxml.etree.Element("test", address="192.168.1.0",
+ netmask="255.255.255.224")),
+ ("192.168.1.31", lxml.etree.Element("test", address="192.168.1.0",
+ netmask="27")),
+ ("10.55.67.191", lxml.etree.Element("test", address="10.55.0.0",
+ netmask="16"))]
+ bad_cases = [
+ ("192.168.1.1", lxml.etree.Element("test", address="192.168.1.2")),
+ ("192.168.2.17", lxml.etree.Element("test", address="192.168.1.0",
+ netmask="24")),
+ ("192.168.2.17", lxml.etree.Element("test", address="192.168.1.0",
+ netmask="255.255.255.0")),
+ ("192.168.1.35", lxml.etree.Element("test", address="192.168.1.0",
+ netmask="255.255.255.224")),
+ ("192.168.1.35", lxml.etree.Element("test", address="192.168.1.0",
+ netmask="27")),
+ ("10.56.67.191", lxml.etree.Element("test", address="10.55.0.0",
+ netmask="16"))]
+ for ip, entry in good_cases:
+ self.assertTrue(ip_matches(ip, entry),
+ "ip_matches(%s, %s) unexpectedly False" %
+ (ip, lxml.etree.tostring(entry)))
+ for ip, entry in bad_cases:
+ self.assertFalse(ip_matches(ip, entry),
+ "ip_matches(%s, %s) unexpectedly True" %
+ (ip, lxml.etree.tostring(entry)))
+
+
+class TestIPACLFile(TestXMLFileBacked):
+ test_obj = IPACLFile
+
+ @patch("Bcfg2.Server.Plugins.ACL.ip_matches")
+ @patch("Bcfg2.Server.Plugins.ACL.rmi_names_equal")
+ def test_check_acl(self, mock_rmi_names_equal, mock_ip_matches):
+ af = self.get_obj()
+ ip = "10.0.0.8"
+ rmi = "ACL.test"
+
+ def reset():
+ mock_rmi_names_equal.reset_mock()
+ mock_ip_matches.reset_mock()
+
+ # test default defer with no entries
+ af.entries = []
+ self.assertIsNone(af.check_acl(ip, rmi))
+
+ # test explicit allow, deny, and defer
+ entries = dict(Allow=lxml.etree.Element("Allow", method=rmi),
+ Deny=lxml.etree.Element("Deny", method=rmi),
+ Defer=lxml.etree.Element("Defer", method=rmi))
+ af.entries = list(entries.values())
+
+ def get_ip_matches(tag):
+ def ip_matches(ip, entry):
+ return entry.tag == tag
+
+ return ip_matches
+
+ mock_rmi_names_equal.return_value = True
+
+ reset()
+ mock_ip_matches.side_effect = get_ip_matches("Allow")
+ self.assertTrue(af.check_acl(ip, rmi))
+ mock_ip_matches.assert_called_with(ip, entries['Allow'])
+ mock_rmi_names_equal.assert_called_with(rmi, rmi)
+
+ reset()
+ mock_ip_matches.side_effect = get_ip_matches("Deny")
+ self.assertFalse(af.check_acl(ip, rmi))
+ mock_ip_matches.assert_called_with(ip, entries['Deny'])
+ mock_rmi_names_equal.assert_called_with(rmi, rmi)
+
+ reset()
+ mock_ip_matches.side_effect = get_ip_matches("Defer")
+ self.assertIsNone(af.check_acl(ip, rmi))
+ mock_ip_matches.assert_called_with(ip, entries['Defer'])
+ mock_rmi_names_equal.assert_called_with(rmi, rmi)
+
+ # test matching RMI names
+ reset()
+ mock_ip_matches.side_effect = lambda i, e: True
+ mock_rmi_names_equal.side_effect = lambda a, b: a == b
+ rmi = "ACL.test2"
+ matching = lxml.etree.Element("Allow", method=rmi)
+ af.entries.append(matching)
+ self.assertTrue(af.check_acl(ip, rmi))
+ mock_ip_matches.assert_called_with(ip, matching)
+ self.assertTrue(
+ call('ACL.test', rmi) in mock_rmi_names_equal.call_args_list or
+ call(rmi, 'ACL.test') in mock_rmi_names_equal.call_args_list)
+
+ # test implicit allow for localhost, defer for others
+ reset()
+ mock_ip_matches.side_effect = lambda i, e: False
+ self.assertIsNone(af.check_acl(ip, rmi))
+
+ reset()
+ self.assertTrue(af.check_acl("127.0.0.1", rmi))
+
+
+class TestMetadataACLFile(TestStructFile):
+ test_obj = MetadataACLFile
+
+ @patch("Bcfg2.Server.Plugins.ACL.rmi_names_equal")
+ def test_check_acl(self, mock_rmi_names_equal):
+ af = self.get_obj()
+ af.Match = Mock()
+ metadata = Mock()
+ mock_rmi_names_equal.side_effect = lambda a, b: a == b
+
+ def reset():
+ af.Match.reset_mock()
+ mock_rmi_names_equal.reset_mock()
+
+ # test default allow
+ af.entries = []
+ self.assertTrue(af.check_acl(metadata, 'ACL.test'))
+
+ # test explicit allow and deny
+ reset()
+ af.entries = [lxml.etree.Element("Allow", method='ACL.test'),
+ lxml.etree.Element("Deny", method='ACL.test2')]
+ af.Match.return_value = af.entries
+ self.assertTrue(af.check_acl(metadata, 'ACL.test'))
+ af.Match.assert_called_with(metadata)
+ self.assertIn(call('ACL.test', 'ACL.test'),
+ mock_rmi_names_equal.call_args_list)
+
+ reset()
+ self.assertFalse(af.check_acl(metadata, 'ACL.test2'))
+ af.Match.assert_called_with(metadata)
+ self.assertIn(call('ACL.test2', 'ACL.test2'),
+ mock_rmi_names_equal.call_args_list)
+
+ # test default deny for non-localhost
+ reset()
+ self.assertFalse(af.check_acl(metadata, 'ACL.test3'))
+ af.Match.assert_called_with(metadata)
+
+ # test default allow for localhost
+ reset()
+ metadata.hostname = 'localhost'
+ self.assertTrue(af.check_acl(metadata, 'ACL.test3'))
+ af.Match.assert_called_with(metadata)
+
+
+class TestACL(TestPlugin, TestClientACLs):
+ test_obj = ACL
+
+ def test_check_acl_ip(self):
+ acl = self.get_obj()
+ acl.ip_acls = Mock()
+ self.assertEqual(acl.check_acl_ip(("192.168.1.10", "12345"),
+ "ACL.test"),
+ acl.ip_acls.check_acl.return_value)
+ acl.ip_acls.check_acl.assert_called_with("192.168.1.10", "ACL.test")
+
+ def test_check_acl_metadata(self):
+ acl = self.get_obj()
+ acl.metadata_acls = Mock()
+ metadata = Mock()
+ self.assertEqual(acl.check_acl_metadata(metadata, "ACL.test"),
+ acl.metadata_acls.check_acl.return_value)
+ acl.metadata_acls.check_acl.assert_called_with(metadata, "ACL.test")
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestBundler.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestBundler.py
new file mode 100644
index 000000000..cfb379c40
--- /dev/null
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestBundler.py
@@ -0,0 +1,111 @@
+import os
+import sys
+import lxml.etree
+from mock import Mock, MagicMock, patch
+from Bcfg2.Server.Plugins.Bundler import *
+
+# 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)
+from common import *
+from TestPlugin import TestStructFile, TestPlugin, TestStructure, \
+ TestXMLDirectoryBacked
+
+
+class TestBundleFile(TestStructFile):
+ test_obj = BundleFile
+ path = os.path.join(datastore, "test", "test1.xml")
+
+ def test_bundle_name(self):
+ cases = [("foo.xml", "foo"),
+ ("foo.bar.xml", "foo.bar"),
+ ("foo-bar-baz.xml", "foo-bar-baz"),
+ ("foo....xml", "foo..."),
+ ("foo.genshi", "foo")]
+ bf = self.get_obj()
+ for fname, bname in cases:
+ bf.name = fname
+ self.assertEqual(bf.bundle_name, bname)
+
+
+class TestBundler(TestPlugin, TestStructure, TestXMLDirectoryBacked):
+ test_obj = Bundler
+
+ def get_obj(self, core=None):
+ @patch("%s.%s.add_directory_monitor" % (self.test_obj.__module__,
+ self.test_obj.__name__),
+ Mock())
+ def inner():
+ return TestPlugin.get_obj(self, core=core)
+ return inner()
+
+ @patch("Bcfg2.Server.Plugin.XMLDirectoryBacked.HandleEvent")
+ def test_HandleEvent(self, mock_HandleEvent):
+ b = self.get_obj()
+ b.bundles = dict(foo=Mock(), bar=Mock())
+ b.entries = {"foo.xml": BundleFile("foo.xml"),
+ "baz.xml": BundleFile("baz.xml")}
+ event = Mock()
+ b.HandleEvent(event)
+ mock_HandleEvent.assert_called_with(b, event)
+ self.assertItemsEqual(b.bundles,
+ dict(foo=b.entries['foo.xml'],
+ baz=b.entries['baz.xml']))
+
+ def test_BuildStructures(self):
+ b = self.get_obj()
+ b.bundles = dict(error=Mock(), skip=Mock(), xinclude=Mock(),
+ has_dep=Mock(), is_dep=Mock(), indep=Mock())
+ expected = dict()
+
+ b.bundles['error'].XMLMatch.side_effect = TemplateError(None)
+
+ xinclude = lxml.etree.Element("Bundle")
+ lxml.etree.SubElement(lxml.etree.SubElement(xinclude, "Bundle"),
+ "Path", name="/test")
+ b.bundles['xinclude'].XMLMatch.return_value = xinclude
+ expected['xinclude'] = lxml.etree.Element("Bundle", name="xinclude")
+ lxml.etree.SubElement(expected['xinclude'], "Path", name="/test")
+
+ has_dep = lxml.etree.Element("Bundle")
+ lxml.etree.SubElement(has_dep, "Bundle", name="is_dep")
+ lxml.etree.SubElement(has_dep, "Package", name="foo")
+ b.bundles['has_dep'].XMLMatch.return_value = has_dep
+ expected['has_dep'] = lxml.etree.Element("Bundle", name="has_dep")
+ lxml.etree.SubElement(expected['has_dep'], "Package", name="foo")
+
+ is_dep = lxml.etree.Element("Bundle")
+ lxml.etree.SubElement(is_dep, "Package", name="bar")
+ b.bundles['is_dep'].XMLMatch.return_value = is_dep
+ expected['is_dep'] = lxml.etree.Element("Bundle", name="is_dep")
+ lxml.etree.SubElement(expected['is_dep'], "Package", name="bar")
+
+ indep = lxml.etree.Element("Bundle", independent="true")
+ lxml.etree.SubElement(indep, "Service", name="baz")
+ b.bundles['indep'].XMLMatch.return_value = indep
+ expected['indep'] = lxml.etree.Element("Independent", name="indep")
+ lxml.etree.SubElement(expected['indep'], "Service", name="baz")
+
+ metadata = Mock()
+ metadata.bundles = ["error", "xinclude", "has_dep", "indep"]
+
+ rv = b.BuildStructures(metadata)
+ self.assertEqual(len(rv), 4)
+ for bundle in rv:
+ name = bundle.get("name")
+ self.assertIsNotNone(name,
+ "Bundle %s was not built" % name)
+ self.assertIn(name, expected,
+ "Unexpected bundle %s was built" % name)
+ self.assertXMLEqual(bundle, expected[name],
+ "Bundle %s was not built correctly" % name)
+ b.bundles[name].XMLMatch.assert_called_with(metadata)
+
+ b.bundles['error'].XMLMatch.assert_called_with(metadata)
+ self.assertFalse(b.bundles['skip'].XMLMatch.called)
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgAuthorizedKeysGenerator.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgAuthorizedKeysGenerator.py
index d655a20cd..f41ae8a46 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgAuthorizedKeysGenerator.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgAuthorizedKeysGenerator.py
@@ -23,12 +23,16 @@ class TestCfgAuthorizedKeysGenerator(TestCfgGenerator, TestStructFile):
test_obj = CfgAuthorizedKeysGenerator
should_monitor = False
- def get_obj(self, name=None, core=None, fam=None):
+ def setUp(self):
+ TestCfgGenerator.setUp(self)
+ TestStructFile.setUp(self)
+
+ @patch("Bcfg2.Server.Plugins.Cfg.CfgAuthorizedKeysGenerator.get_cfg")
+ def get_obj(self, mock_get_cfg, name=None, core=None, fam=None):
if name is None:
name = self.path
- Bcfg2.Server.Plugins.Cfg.CfgAuthorizedKeysGenerator.CFG = Mock()
if core is not None:
- Bcfg2.Server.Plugins.Cfg.CfgAuthorizedKeysGenerator.CFG.core = core
+ mock_get_cfg.return_value.core = core
return self.test_obj(name)
@patch("Bcfg2.Server.Plugins.Cfg.CfgGenerator.handle_event")
@@ -40,33 +44,9 @@ class TestCfgAuthorizedKeysGenerator(TestCfgGenerator, TestStructFile):
mock_HandleEvent.assert_called_with(akg, evt)
mock_handle_event.assert_called_with(akg, evt)
- def test_category(self):
- akg = self.get_obj()
- cfp = Mock()
- cfp.has_section.return_value = False
- cfp.has_option.return_value = False
- Bcfg2.Server.Plugins.Cfg.CfgAuthorizedKeysGenerator.SETUP = Mock()
- Bcfg2.Server.Plugins.Cfg.CfgAuthorizedKeysGenerator.SETUP.cfp = cfp
-
- self.assertIsNone(akg.category)
- cfp.has_section.assert_called_with("sshkeys")
-
- cfp.reset_mock()
- cfp.has_section.return_value = True
- self.assertIsNone(akg.category)
- cfp.has_section.assert_called_with("sshkeys")
- cfp.has_option.assert_called_with("sshkeys", "category")
-
- cfp.reset_mock()
- cfp.has_option.return_value = True
- self.assertEqual(akg.category, cfp.get.return_value)
- cfp.has_section.assert_called_with("sshkeys")
- cfp.has_option.assert_called_with("sshkeys", "category")
- cfp.get.assert_called_with("sshkeys", "category")
-
@patch("Bcfg2.Server.Plugins.Cfg.CfgAuthorizedKeysGenerator.ClientMetadata")
- @patch("Bcfg2.Server.Plugins.Cfg.CfgAuthorizedKeysGenerator.CfgAuthorizedKeysGenerator.category", "category")
def test_get_data(self, mock_ClientMetadata):
+ Bcfg2.Options.setup.sshkeys_category = "category"
akg = self.get_obj()
akg.XMLMatch = Mock()
@@ -131,17 +111,18 @@ class TestCfgAuthorizedKeysGenerator(TestCfgGenerator, TestStructFile):
reset()
host = "baz.example.com"
spec = lxml.etree.Element("AuthorizedKeys")
- lxml.etree.SubElement(
- lxml.etree.SubElement(spec,
- "Allow",
- attrib={"from": pubkey, "host": host}),
- "Params", foo="foo", bar="bar=bar")
+ allow = lxml.etree.SubElement(spec, "Allow",
+ attrib={"from": pubkey, "host": host})
+ lxml.etree.SubElement(allow, "Option", name="foo", value="foo")
+ lxml.etree.SubElement(allow, "Option", name="bar")
+ lxml.etree.SubElement(allow, "Option", name="baz", value="baz=baz")
akg.XMLMatch.return_value = spec
params, actual_host, actual_pubkey = akg.get_data(entry,
metadata).split()
self.assertEqual(actual_host, host)
self.assertEqual(actual_pubkey, pubkey)
- self.assertItemsEqual(params.split(","), ["foo=foo", "bar=bar=bar"])
+ self.assertItemsEqual(params.split(","), ["foo=foo", "bar",
+ "baz=baz=baz"])
akg.XMLMatch.assert_called_with(metadata)
akg.core.build_metadata.assert_called_with(host)
self.assertEqual(akg.core.Bind.call_args[0][0].get("name"), pubkey)
@@ -151,10 +132,10 @@ class TestCfgAuthorizedKeysGenerator(TestCfgGenerator, TestStructFile):
spec = lxml.etree.Element("AuthorizedKeys")
text = lxml.etree.SubElement(spec, "Allow")
text.text = "ssh-rsa publickey /foo/bar\n"
- lxml.etree.SubElement(text, "Params", foo="foo")
+ lxml.etree.SubElement(text, "Option", name="foo")
akg.XMLMatch.return_value = spec
self.assertEqual(akg.get_data(entry, metadata),
- "foo=foo %s" % text.text.strip())
+ "foo %s" % text.text.strip())
akg.XMLMatch.assert_called_with(metadata)
self.assertFalse(akg.core.build_metadata.called)
self.assertFalse(akg.core.Bind.called)
@@ -163,7 +144,7 @@ class TestCfgAuthorizedKeysGenerator(TestCfgGenerator, TestStructFile):
lxml.etree.SubElement(spec, "Allow", attrib={"from": pubkey})
akg.XMLMatch.return_value = spec
self.assertItemsEqual(akg.get_data(entry, metadata).splitlines(),
- ["foo=foo %s" % text.text.strip(),
+ ["foo %s" % text.text.strip(),
"profile %s" % pubkey])
akg.XMLMatch.assert_called_with(metadata)
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgCheetahGenerator.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgCheetahGenerator.py
index fc5d5e53d..93331304a 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgCheetahGenerator.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgCheetahGenerator.py
@@ -17,32 +17,39 @@ from common import *
from TestServer.TestPlugins.TestCfg.Test_init import TestCfgGenerator
-if HAS_CHEETAH or can_skip:
- class TestCfgCheetahGenerator(TestCfgGenerator):
- test_obj = CfgCheetahGenerator
+class TestCfgCheetahGenerator(TestCfgGenerator):
+ test_obj = CfgCheetahGenerator
- @skipUnless(HAS_CHEETAH, "Cheetah libraries not found, skipping")
- def setUp(self):
- pass
+ @skipUnless(HAS_CHEETAH, "Cheetah libraries not found, skipping")
+ def setUp(self):
+ TestCfgGenerator.setUp(self)
+ set_setup_default("repository", datastore)
- @patch("Bcfg2.Server.Plugins.Cfg.CfgCheetahGenerator.Template")
- def test_get_data(self, mock_Template):
- ccg = self.get_obj(encoding='UTF-8')
- ccg.data = "data"
- entry = lxml.etree.Element("Path", name="/test.txt")
- metadata = Mock()
- Bcfg2.Server.Plugins.Cfg.CfgCheetahGenerator.SETUP = MagicMock()
+ @patch("Bcfg2.Server.Plugins.Cfg.CfgCheetahGenerator.Template")
+ @patch("Bcfg2.Server.Plugins.Cfg.CfgCheetahGenerator.get_template_data")
+ def test_get_data(self, mock_get_template_data, mock_Template):
+ ccg = self.get_obj()
+ ccg.data = "data"
+ entry = lxml.etree.Element("Path", name="/test.txt")
+ metadata = Mock()
- self.assertEqual(ccg.get_data(entry, metadata),
- mock_Template.return_value.respond.return_value)
- Bcfg2.Server.Plugins.Cfg.CfgCheetahGenerator.SETUP.__getitem__.assert_called_with("repo")
- mock_Template.assert_called_with("data".decode(ccg.encoding),
- compilerSettings=ccg.settings)
- tmpl = mock_Template.return_value
- tmpl.respond.assert_called_with()
- self.assertEqual(tmpl.metadata, metadata)
- self.assertEqual(tmpl.name, entry.get("name"))
- self.assertEqual(tmpl.path, entry.get("name"))
- self.assertEqual(tmpl.source_path, ccg.name)
- self.assertEqual(tmpl.repo,
- Bcfg2.Server.Plugins.Cfg.CfgCheetahGenerator.SETUP.__getitem__.return_value)
+ template_vars = dict(name=entry.get("name"),
+ metadata=metadata,
+ path=ccg.name,
+ source_path=ccg.name,
+ repo=datastore)
+ mock_get_template_data.return_value = template_vars
+
+ self.assertEqual(ccg.get_data(entry, metadata),
+ mock_Template.return_value.respond.return_value)
+ mock_Template.assert_called_with(
+ "data".decode(Bcfg2.Options.setup.encoding),
+ compilerSettings=ccg.settings)
+ tmpl = mock_Template.return_value
+ tmpl.respond.assert_called_with()
+ for key, val in template_vars.items():
+ self.assertEqual(getattr(tmpl, key), val)
+ self.assertItemsEqual(mock_get_template_data.call_args[0],
+ [entry, metadata, ccg.name])
+ self.assertIsInstance(mock_get_template_data.call_args[1]['default'],
+ DefaultCheetahDataProvider)
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgEncryptedCheetahGenerator.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgEncryptedCheetahGenerator.py
index 46062569d..4c987551b 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgEncryptedCheetahGenerator.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgEncryptedCheetahGenerator.py
@@ -30,18 +30,17 @@ except ImportError:
HAS_CRYPTO = False
-if can_skip or (HAS_CRYPTO and HAS_CHEETAH):
- class TestCfgEncryptedCheetahGenerator(TestCfgCheetahGenerator,
- TestCfgEncryptedGenerator):
- test_obj = CfgEncryptedCheetahGenerator
+class TestCfgEncryptedCheetahGenerator(TestCfgCheetahGenerator,
+ TestCfgEncryptedGenerator):
+ test_obj = CfgEncryptedCheetahGenerator
- @skipUnless(HAS_CRYPTO, "Encryption libraries not found, skipping")
- @skipUnless(HAS_CHEETAH, "Cheetah libraries not found, skipping")
- def setUp(self):
- pass
+ @skipUnless(HAS_CRYPTO, "Encryption libraries not found, skipping")
+ @skipUnless(HAS_CHEETAH, "Cheetah libraries not found, skipping")
+ def setUp(self):
+ pass
- def test_handle_event(self):
- TestCfgEncryptedGenerator.test_handle_event(self)
+ def test_handle_event(self):
+ TestCfgEncryptedGenerator.test_handle_event(self)
- def test_get_data(self):
- TestCfgCheetahGenerator.test_get_data(self)
+ def test_get_data(self):
+ TestCfgCheetahGenerator.test_get_data(self)
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgEncryptedGenerator.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgEncryptedGenerator.py
index 2bfec0e2d..03b9fb0f4 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgEncryptedGenerator.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgEncryptedGenerator.py
@@ -19,60 +19,53 @@ from common import *
from TestServer.TestPlugins.TestCfg.Test_init import TestCfgGenerator
-if can_skip or HAS_CRYPTO:
- class TestCfgEncryptedGenerator(TestCfgGenerator):
- test_obj = CfgEncryptedGenerator
+class TestCfgEncryptedGenerator(TestCfgGenerator):
+ test_obj = CfgEncryptedGenerator
- @skipUnless(HAS_CRYPTO, "Encryption libraries not found, skipping")
- def setUp(self):
- pass
+ @skipUnless(HAS_CRYPTO, "Encryption libraries not found, skipping")
+ def setUp(self):
+ TestCfgGenerator.setUp(self)
- @patchIf(HAS_CRYPTO,
- "Bcfg2.Server.Plugins.Cfg.CfgEncryptedGenerator.get_algorithm")
- @patchIf(HAS_CRYPTO,
- "Bcfg2.Server.Plugins.Cfg.CfgEncryptedGenerator.bruteforce_decrypt")
- def test_handle_event(self, mock_decrypt, mock_get_algorithm):
- @patch("Bcfg2.Server.Plugins.Cfg.CfgGenerator.handle_event")
- def inner(mock_handle_event):
- def reset():
- mock_decrypt.reset_mock()
- mock_get_algorithm.reset_mock()
- mock_handle_event.reset_mock()
+ @patchIf(HAS_CRYPTO,
+ "Bcfg2.Server.Plugins.Cfg.CfgEncryptedGenerator.bruteforce_decrypt")
+ def test_handle_event(self, mock_decrypt):
+ @patch("Bcfg2.Server.Plugins.Cfg.CfgGenerator.handle_event")
+ def inner(mock_handle_event):
+ def reset():
+ mock_decrypt.reset_mock()
+ mock_handle_event.reset_mock()
- def get_event_data(obj, event):
- obj.data = "encrypted"
+ def get_event_data(obj, event):
+ obj.data = "encrypted"
- mock_handle_event.side_effect = get_event_data
- mock_decrypt.side_effect = lambda d, **kw: "plaintext"
- event = Mock()
- ceg = self.get_obj()
- ceg.handle_event(event)
- mock_handle_event.assert_called_with(ceg, event)
- mock_decrypt.assert_called_with(
- "encrypted",
- setup=Bcfg2.Server.Plugins.Cfg.SETUP,
- algorithm=mock_get_algorithm.return_value)
- self.assertEqual(ceg.data, "plaintext")
+ mock_handle_event.side_effect = get_event_data
+ mock_decrypt.side_effect = lambda d, **kw: "plaintext"
+ event = Mock()
+ ceg = self.get_obj()
+ ceg.handle_event(event)
+ mock_handle_event.assert_called_with(ceg, event)
+ mock_decrypt.assert_called_with("encrypted")
+ self.assertEqual(ceg.data, "plaintext")
- reset()
- mock_decrypt.side_effect = EVPError
- self.assertRaises(PluginExecutionError,
- ceg.handle_event, event)
- inner()
+ reset()
+ mock_decrypt.side_effect = EVPError
+ self.assertRaises(PluginExecutionError,
+ ceg.handle_event, event)
+ inner()
- # to perform the tests from the parent test object, we
- # make bruteforce_decrypt just return whatever data was
- # given to it
- mock_decrypt.side_effect = lambda d, **kw: d
- TestCfgGenerator.test_handle_event(self)
+ # to perform the tests from the parent test object, we
+ # make bruteforce_decrypt just return whatever data was
+ # given to it
+ mock_decrypt.side_effect = lambda d, **kw: d
+ TestCfgGenerator.test_handle_event(self)
- def test_get_data(self):
- ceg = self.get_obj()
- ceg.data = None
- entry = lxml.etree.Element("Path", name="/test.txt")
- metadata = Mock()
+ def test_get_data(self):
+ ceg = self.get_obj()
+ ceg.data = None
+ entry = lxml.etree.Element("Path", name="/test.txt")
+ metadata = Mock()
- self.assertRaises(PluginExecutionError,
- ceg.get_data, entry, metadata)
+ self.assertRaises(PluginExecutionError,
+ ceg.get_data, entry, metadata)
- TestCfgGenerator.test_get_data(self)
+ TestCfgGenerator.test_get_data(self)
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgEncryptedGenshiGenerator.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgEncryptedGenshiGenerator.py
index b447a9bb8..0b74e4a60 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgEncryptedGenshiGenerator.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgEncryptedGenshiGenerator.py
@@ -14,20 +14,13 @@ while path != "/":
path = os.path.dirname(path)
from common import *
-try:
- from TestServer.TestPlugins.TestCfg.TestCfgGenshiGenerator import \
- TestCfgGenshiGenerator
- HAS_GENSHI = True
-except ImportError:
- TestCfgGenshiGenerator = object
- HAS_GENSHI = False
+from TestServer.TestPlugins.TestCfg.TestCfgGenshiGenerator import \
+ TestCfgGenshiGenerator
-if can_skip or (HAS_CRYPTO and HAS_GENSHI):
- class TestCfgEncryptedGenshiGenerator(TestCfgGenshiGenerator):
- test_obj = CfgEncryptedGenshiGenerator
+class TestCfgEncryptedGenshiGenerator(TestCfgGenshiGenerator):
+ test_obj = CfgEncryptedGenshiGenerator
- @skipUnless(HAS_CRYPTO, "Encryption libraries not found, skipping")
- @skipUnless(HAS_GENSHI, "Genshi libraries not found, skipping")
- def setUp(self):
- pass
+ @skipUnless(HAS_CRYPTO, "Encryption libraries not found, skipping")
+ def setUp(self):
+ TestCfgGenshiGenerator.setUp(self)
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgExternalCommandVerifier.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgExternalCommandVerifier.py
index 0f369113b..7ceedb7c2 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgExternalCommandVerifier.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgExternalCommandVerifier.py
@@ -21,35 +21,32 @@ from TestServer.TestPlugins.TestCfg.Test_init import TestCfgVerifier
class TestCfgExternalCommandVerifier(TestCfgVerifier):
test_obj = CfgExternalCommandVerifier
- @patch("Bcfg2.Server.Plugins.Cfg.CfgExternalCommandVerifier.Popen")
- def test_verify_entry(self, mock_Popen):
- proc = Mock()
- mock_Popen.return_value = proc
- proc.wait.return_value = 0
- proc.communicate.return_value = ("stdout", "stderr")
+ def test_verify_entry(self):
entry = lxml.etree.Element("Path", name="/test.txt")
metadata = Mock()
ecv = self.get_obj()
ecv.cmd = ["/bin/bash", "-x", "foo"]
+ ecv.exc = Mock()
+ ecv.exc.run.return_value = Mock()
+ ecv.exc.run.return_value.success = True
+
ecv.verify_entry(entry, metadata, "data")
- self.assertEqual(mock_Popen.call_args[0], (ecv.cmd,))
- proc.communicate.assert_called_with(input="data")
- proc.wait.assert_called_with()
+ ecv.exc.run.assert_called_with(ecv.cmd, inputdata="data")
- mock_Popen.reset_mock()
- proc.wait.return_value = 13
+ ecv.exc.reset_mock()
+ ecv.exc.run.return_value.success = False
self.assertRaises(CfgVerificationError,
ecv.verify_entry, entry, metadata, "data")
- self.assertEqual(mock_Popen.call_args[0], (ecv.cmd,))
- proc.communicate.assert_called_with(input="data")
- proc.wait.assert_called_with()
+ ecv.exc.run.assert_called_with(ecv.cmd, inputdata="data")
+
+ ecv.exc.reset_mock()
- mock_Popen.reset_mock()
- mock_Popen.side_effect = OSError
+ ecv.exc.reset_mock()
+ ecv.exc.run.side_effect = OSError
self.assertRaises(CfgVerificationError,
ecv.verify_entry, entry, metadata, "data")
- self.assertEqual(mock_Popen.call_args[0], (ecv.cmd,))
+ ecv.exc.run.assert_called_with(ecv.cmd, inputdata="data")
@patch("os.access")
def test_handle_event(self, mock_access):
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgGenshiGenerator.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgGenshiGenerator.py
index 2e8b7bfa5..b667d417a 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgGenshiGenerator.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgGenshiGenerator.py
@@ -19,111 +19,120 @@ from common import *
from TestServer.TestPlugins.TestCfg.Test_init import TestCfgGenerator
-if can_skip or HAS_GENSHI:
- class TestCfgGenshiGenerator(TestCfgGenerator):
- test_obj = CfgGenshiGenerator
-
- @skipUnless(HAS_GENSHI, "Genshi libraries not found, skipping")
- def setUp(self):
- pass
-
- def test_removecomment(self):
- data = [(None, "test", 1),
- (None, "test2", 2)]
- stream = [(genshi.core.COMMENT, "test", 0),
- data[0],
- (genshi.core.COMMENT, "test3", 0),
- data[1]]
- self.assertItemsEqual(list(removecomment(stream)), data)
-
- def test__init(self):
- TestCfgGenerator.test__init(self)
- cgg = self.get_obj()
- self.assertIsInstance(cgg.loader, cgg.__loader_cls__)
-
- def test_get_data(self):
- cgg = self.get_obj()
- cgg._handle_genshi_exception = Mock()
- cgg.template = Mock()
- fltr = Mock()
- cgg.template.generate.return_value = fltr
- stream = Mock()
- fltr.filter.return_value = stream
- entry = lxml.etree.Element("Path", name="/test.txt")
- metadata = Mock()
-
- Bcfg2.Server.Plugins.Cfg.CfgGenshiGenerator.SETUP = MagicMock()
-
- def reset():
- cgg.template.reset_mock()
- cgg._handle_genshi_exception.reset_mock()
- Bcfg2.Server.Plugins.Cfg.CfgGenshiGenerator.SETUP.reset_mock()
-
- template_vars = dict(
- name=entry.get("name"),
- metadata=metadata,
- path=cgg.name,
- source_path=cgg.name,
- repo=Bcfg2.Server.Plugins.Cfg.CfgGenshiGenerator.SETUP.__getitem__.return_value)
-
- self.assertEqual(cgg.get_data(entry, metadata),
- stream.render.return_value)
- cgg.template.generate.assert_called_with(**template_vars)
- Bcfg2.Server.Plugins.Cfg.CfgGenshiGenerator.SETUP.__getitem__.assert_called_with("repo")
- fltr.filter.assert_called_with(removecomment)
- stream.render.assert_called_with("text", encoding=cgg.encoding,
- strip_whitespace=False)
-
- reset()
- def render(fmt, **kwargs):
- stream.render.side_effect = None
- raise TypeError
- stream.render.side_effect = render
- self.assertEqual(cgg.get_data(entry, metadata),
- stream.render.return_value)
- cgg.template.generate.assert_called_with(**template_vars)
- Bcfg2.Server.Plugins.Cfg.CfgGenshiGenerator.SETUP.__getitem__.assert_called_with("repo")
- fltr.filter.assert_called_with(removecomment)
- self.assertEqual(stream.render.call_args_list,
- [call("text", encoding=cgg.encoding,
- strip_whitespace=False),
- call("text", encoding=cgg.encoding)])
-
- reset()
- stream.render.side_effect = UndefinedError("test")
- self.assertRaises(UndefinedError,
- cgg.get_data, entry, metadata)
- cgg.template.generate.assert_called_with(**template_vars)
- Bcfg2.Server.Plugins.Cfg.CfgGenshiGenerator.SETUP.__getitem__.assert_called_with("repo")
- fltr.filter.assert_called_with(removecomment)
- stream.render.assert_called_with("text", encoding=cgg.encoding,
- strip_whitespace=False)
-
- reset()
- stream.render.side_effect = ValueError
- cgg._handle_genshi_exception.side_effect = ValueError
- self.assertRaises(ValueError,
- cgg.get_data, entry, metadata)
- cgg.template.generate.assert_called_with(**template_vars)
- Bcfg2.Server.Plugins.Cfg.CfgGenshiGenerator.SETUP.__getitem__.assert_called_with("repo")
- fltr.filter.assert_called_with(removecomment)
- stream.render.assert_called_with("text", encoding=cgg.encoding,
- strip_whitespace=False)
- self.assertTrue(cgg._handle_genshi_exception.called)
-
- def test_handle_event(self):
- cgg = self.get_obj()
- cgg.loader = Mock()
- event = Mock()
- cgg.handle_event(event)
- cgg.loader.load.assert_called_with(cgg.name,
- cls=NewTextTemplate,
- encoding=cgg.encoding)
-
- cgg.loader.reset_mock()
- cgg.loader.load.side_effect = OSError
- self.assertRaises(PluginExecutionError,
- cgg.handle_event, event)
- cgg.loader.load.assert_called_with(cgg.name,
- cls=NewTextTemplate,
- encoding=cgg.encoding)
+class TestCfgGenshiGenerator(TestCfgGenerator):
+ test_obj = CfgGenshiGenerator
+
+ def setUp(self):
+ TestCfgGenerator.setUp(self)
+ set_setup_default("repository", datastore)
+
+ def test__init(self):
+ TestCfgGenerator.test__init(self)
+ cgg = self.get_obj()
+ self.assertIsInstance(cgg.loader, cgg.__loader_cls__)
+
+ @patch("Bcfg2.Server.Plugins.Cfg.CfgGenshiGenerator.get_template_data")
+ def test_get_data(self, mock_get_template_data):
+ cgg = self.get_obj()
+ cgg._handle_genshi_exception = Mock()
+ cgg.template = Mock()
+ fltr = Mock()
+ cgg.template.generate.return_value = fltr
+ stream = Mock()
+ fltr.filter.return_value = stream
+ entry = lxml.etree.Element("Path", name="/test.txt")
+ metadata = Mock()
+
+ def reset():
+ cgg.template.reset_mock()
+ cgg._handle_genshi_exception.reset_mock()
+ mock_get_template_data.reset_mock()
+
+ template_vars = dict(name=entry.get("name"),
+ metadata=metadata,
+ path=cgg.name,
+ source_path=cgg.name,
+ repo=datastore)
+ mock_get_template_data.return_value = template_vars
+
+ self.assertEqual(cgg.get_data(entry, metadata),
+ stream.render.return_value)
+ cgg.template.generate.assert_called_with(**template_vars)
+ self.assertItemsEqual(mock_get_template_data.call_args[0],
+ [entry, metadata, cgg.name])
+ self.assertIsInstance(mock_get_template_data.call_args[1]['default'],
+ DefaultGenshiDataProvider)
+ fltr.filter.assert_called_with(removecomment)
+ stream.render.assert_called_with(
+ "text",
+ encoding=Bcfg2.Options.setup.encoding,
+ strip_whitespace=False)
+
+ reset()
+ def render(fmt, **kwargs):
+ stream.render.side_effect = None
+ raise TypeError
+ stream.render.side_effect = render
+ self.assertEqual(cgg.get_data(entry, metadata),
+ stream.render.return_value)
+ cgg.template.generate.assert_called_with(**template_vars)
+ self.assertItemsEqual(mock_get_template_data.call_args[0],
+ [entry, metadata, cgg.name])
+ self.assertIsInstance(mock_get_template_data.call_args[1]['default'],
+ DefaultGenshiDataProvider)
+ fltr.filter.assert_called_with(removecomment)
+ self.assertEqual(stream.render.call_args_list,
+ [call("text",
+ encoding=Bcfg2.Options.setup.encoding,
+ strip_whitespace=False),
+ call("text",
+ encoding=Bcfg2.Options.setup.encoding)])
+
+ reset()
+ stream.render.side_effect = UndefinedError("test")
+ self.assertRaises(UndefinedError,
+ cgg.get_data, entry, metadata)
+ cgg.template.generate.assert_called_with(**template_vars)
+ self.assertItemsEqual(mock_get_template_data.call_args[0],
+ [entry, metadata, cgg.name])
+ self.assertIsInstance(mock_get_template_data.call_args[1]['default'],
+ DefaultGenshiDataProvider)
+ fltr.filter.assert_called_with(removecomment)
+ stream.render.assert_called_with("text",
+ encoding=Bcfg2.Options.setup.encoding,
+ strip_whitespace=False)
+
+ reset()
+ stream.render.side_effect = ValueError
+ cgg._handle_genshi_exception.side_effect = ValueError
+ self.assertRaises(ValueError,
+ cgg.get_data, entry, metadata)
+ cgg.template.generate.assert_called_with(**template_vars)
+ self.assertItemsEqual(mock_get_template_data.call_args[0],
+ [entry, metadata, cgg.name])
+ self.assertIsInstance(mock_get_template_data.call_args[1]['default'],
+ DefaultGenshiDataProvider)
+ fltr.filter.assert_called_with(removecomment)
+ stream.render.assert_called_with("text",
+ encoding=Bcfg2.Options.setup.encoding,
+ strip_whitespace=False)
+ self.assertTrue(cgg._handle_genshi_exception.called)
+
+ def test_handle_event(self):
+ cgg = self.get_obj()
+ cgg.loader = Mock()
+ event = Mock()
+ cgg.handle_event(event)
+ cgg.loader.load.assert_called_with(
+ cgg.name,
+ cls=NewTextTemplate,
+ encoding=Bcfg2.Options.setup.encoding)
+
+ cgg.loader.reset_mock()
+ cgg.loader.load.side_effect = OSError
+ self.assertRaises(PluginExecutionError,
+ cgg.handle_event, event)
+ cgg.loader.load.assert_called_with(
+ cgg.name,
+ cls=NewTextTemplate,
+ encoding=Bcfg2.Options.setup.encoding)
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgInfoXML.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgInfoXML.py
index 839e9c3b8..349da2213 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgInfoXML.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgInfoXML.py
@@ -21,53 +21,26 @@ from TestServer.TestPlugins.TestCfg.Test_init import TestCfgInfo
class TestCfgInfoXML(TestCfgInfo):
test_obj = CfgInfoXML
+ def setUp(self):
+ TestCfgInfo.setUp(self)
+ set_setup_default("filemonitor", MagicMock())
+
def test__init(self):
TestCfgInfo.test__init(self)
ci = self.get_obj()
self.assertIsInstance(ci.infoxml, InfoXML)
def test_bind_info_to_entry(self):
- entry = lxml.etree.Element("Path", name="/test.txt")
- metadata = Mock()
ci = self.get_obj()
ci.infoxml = Mock()
- ci._set_info = Mock()
-
- self.assertRaises(PluginExecutionError,
- ci.bind_info_to_entry, entry, metadata)
- ci.infoxml.pnode.Match.assert_called_with(metadata, dict(),
- entry=entry)
- self.assertFalse(ci._set_info.called)
-
- ci.infoxml.reset_mock()
- ci._set_info.reset_mock()
- mdata_value = Mock()
- def set_mdata(metadata, mdata, entry=None):
- mdata['Info'] = {None: mdata_value}
+ entry = Mock()
+ metadata = Mock()
- ci.infoxml.pnode.Match.side_effect = set_mdata
ci.bind_info_to_entry(entry, metadata)
- ci.infoxml.pnode.Match.assert_called_with(metadata,
- dict(Info={None: mdata_value}),
- entry=entry)
- ci._set_info.assert_called_with(entry, mdata_value)
+ ci.infoxml.BindEntry.assert_called_with(entry, metadata)
def test_handle_event(self):
ci = self.get_obj()
ci.infoxml = Mock()
ci.handle_event(Mock)
ci.infoxml.HandleEvent.assert_called_with()
-
- def test__set_info(self):
- @patch("Bcfg2.Server.Plugins.Cfg.CfgInfo._set_info")
- def inner(mock_set_info):
- ci = self.get_obj()
- entry = Mock()
- info = {"foo": "foo",
- "__children__": ["one", "two"]}
- ci._set_info(entry, info)
- self.assertItemsEqual(entry.append.call_args_list,
- [call(c) for c in info['__children__']])
-
- inner()
- TestCfgInfo.test__set_info(self)
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgPrivateKeyCreator.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgPrivateKeyCreator.py
index e139a592b..d64bbaabf 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgPrivateKeyCreator.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgPrivateKeyCreator.py
@@ -7,7 +7,7 @@ from Bcfg2.Server.Plugins.Cfg.CfgPrivateKeyCreator import *
from Bcfg2.Server.Plugin import PluginExecutionError
import Bcfg2.Server.Plugins.Cfg.CfgPrivateKeyCreator
try:
- from Bcfg2.Encryption import EVPError
+ from Bcfg2.Server.Encryption import EVPError
HAS_CRYPTO = True
except:
HAS_CRYPTO = False
@@ -22,100 +22,36 @@ while path != "/":
break
path = os.path.dirname(path)
from common import *
-from TestServer.TestPlugins.TestCfg.Test_init import TestCfgCreator
-from TestServer.TestPlugin.Testhelpers import TestStructFile
+from TestServer.TestPlugins.TestCfg.Test_init import TestXMLCfgCreator
-class TestCfgPrivateKeyCreator(TestCfgCreator, TestStructFile):
+class TestCfgPrivateKeyCreator(TestXMLCfgCreator):
test_obj = CfgPrivateKeyCreator
should_monitor = False
+ @patch("Bcfg2.Server.Plugins.Cfg.CfgPublicKeyCreator.get_cfg", Mock())
def get_obj(self, name=None, fam=None):
- Bcfg2.Server.Plugins.Cfg.CfgPublicKeyCreator.CFG = Mock()
- return TestCfgCreator.get_obj(self, name=name)
-
- @patch("Bcfg2.Server.Plugins.Cfg.CfgCreator.handle_event")
- @patch("Bcfg2.Server.Plugin.helpers.StructFile.HandleEvent")
- def test_handle_event(self, mock_HandleEvent, mock_handle_event):
- pkc = self.get_obj()
- evt = Mock()
- pkc.handle_event(evt)
- mock_HandleEvent.assert_called_with(pkc, evt)
- mock_handle_event.assert_called_with(pkc, evt)
-
- def test_category(self):
- pkc = self.get_obj()
- cfp = Mock()
- cfp.has_section.return_value = False
- cfp.has_option.return_value = False
- Bcfg2.Server.Plugins.Cfg.CfgPrivateKeyCreator.SETUP = Mock()
- Bcfg2.Server.Plugins.Cfg.CfgPrivateKeyCreator.SETUP.cfp = cfp
-
- self.assertIsNone(pkc.category)
- cfp.has_section.assert_called_with("sshkeys")
-
- cfp.reset_mock()
- cfp.has_section.return_value = True
- self.assertIsNone(pkc.category)
- cfp.has_section.assert_called_with("sshkeys")
- cfp.has_option.assert_called_with("sshkeys", "category")
-
- cfp.reset_mock()
- cfp.has_option.return_value = True
- self.assertEqual(pkc.category, cfp.get.return_value)
- cfp.has_section.assert_called_with("sshkeys")
- cfp.has_option.assert_called_with("sshkeys", "category")
- cfp.get.assert_called_with("sshkeys", "category")
-
- @skipUnless(HAS_CRYPTO, "No crypto libraries found, skipping")
- @patchIf(HAS_CRYPTO, "Bcfg2.Encryption.get_passphrases")
- def test_passphrase(self, mock_get_passphrases):
- pkc = self.get_obj()
- cfp = Mock()
- cfp.has_section.return_value = False
- cfp.has_option.return_value = False
- Bcfg2.Server.Plugins.Cfg.CfgPrivateKeyCreator.SETUP = Mock()
- Bcfg2.Server.Plugins.Cfg.CfgPrivateKeyCreator.SETUP.cfp = cfp
-
- self.assertIsNone(pkc.passphrase)
- cfp.has_section.assert_called_with("sshkeys")
-
- cfp.reset_mock()
- cfp.has_section.return_value = True
- self.assertIsNone(pkc.passphrase)
- cfp.has_section.assert_called_with("sshkeys")
- cfp.has_option.assert_called_with("sshkeys", "passphrase")
-
- cfp.reset_mock()
- cfp.get.return_value = "test"
- mock_get_passphrases.return_value = dict(test="foo", test2="bar")
- cfp.has_option.return_value = True
- self.assertEqual(pkc.passphrase, "foo")
- cfp.has_section.assert_called_with("sshkeys")
- cfp.has_option.assert_called_with("sshkeys", "passphrase")
- cfp.get.assert_called_with("sshkeys", "passphrase")
- mock_get_passphrases.assert_called_with(Bcfg2.Server.Plugins.Cfg.CfgPrivateKeyCreator.SETUP)
+ return TestXMLCfgCreator.get_obj(self, name=name)
@patch("shutil.rmtree")
@patch("tempfile.mkdtemp")
- @patch("subprocess.Popen")
- def test__gen_keypair(self, mock_Popen, mock_mkdtemp, mock_rmtree):
+ def test__gen_keypair(self, mock_mkdtemp, mock_rmtree):
pkc = self.get_obj()
+ pkc.cmd = Mock()
pkc.XMLMatch = Mock()
mock_mkdtemp.return_value = datastore
metadata = Mock()
- proc = Mock()
- proc.wait.return_value = 0
- proc.communicate.return_value = MagicMock()
- mock_Popen.return_value = proc
+ exc = Mock()
+ exc.success = True
+ pkc.cmd.run.return_value = exc
spec = lxml.etree.Element("PrivateKey")
pkc.XMLMatch.return_value = spec
def reset():
pkc.XMLMatch.reset_mock()
- mock_Popen.reset_mock()
+ pkc.cmd.reset_mock()
mock_mkdtemp.reset_mock()
mock_rmtree.reset_mock()
@@ -123,10 +59,9 @@ class TestCfgPrivateKeyCreator(TestCfgCreator, TestStructFile):
os.path.join(datastore, "privkey"))
pkc.XMLMatch.assert_called_with(metadata)
mock_mkdtemp.assert_called_with()
- self.assertItemsEqual(mock_Popen.call_args[0][0],
- ["ssh-keygen", "-f",
- os.path.join(datastore, "privkey"),
- "-t", "rsa", "-N", ""])
+ pkc.cmd.run.assert_called_with(["ssh-keygen", "-f",
+ os.path.join(datastore, "privkey"),
+ "-t", "rsa", "-N", ""])
reset()
lxml.etree.SubElement(spec, "Params", bits="768", type="dsa")
@@ -137,73 +72,15 @@ class TestCfgPrivateKeyCreator(TestCfgCreator, TestStructFile):
os.path.join(datastore, "privkey"))
pkc.XMLMatch.assert_called_with(metadata)
mock_mkdtemp.assert_called_with()
- self.assertItemsEqual(mock_Popen.call_args[0][0],
- ["ssh-keygen", "-f",
- os.path.join(datastore, "privkey"),
- "-t", "dsa", "-b", "768", "-N", "foo"])
+ pkc.cmd.run.assert_called_with(["ssh-keygen", "-f",
+ os.path.join(datastore, "privkey"),
+ "-t", "dsa", "-b", "768", "-N", "foo"])
reset()
- proc.wait.return_value = 1
+ pkc.cmd.run.return_value.success = False
self.assertRaises(CfgCreationError, pkc._gen_keypair, metadata)
mock_rmtree.assert_called_with(datastore)
- def test_get_specificity(self):
- pkc = self.get_obj()
- pkc.XMLMatch = Mock()
-
- metadata = Mock()
-
- def reset():
- pkc.XMLMatch.reset_mock()
- metadata.group_in_category.reset_mock()
-
- category = "Bcfg2.Server.Plugins.Cfg.CfgPrivateKeyCreator.CfgPrivateKeyCreator.category"
- @patch(category, None)
- def inner():
- pkc.XMLMatch.return_value = lxml.etree.Element("PrivateKey")
- self.assertItemsEqual(pkc.get_specificity(metadata),
- dict(host=metadata.hostname))
- inner()
-
- @patch(category, "foo")
- def inner2():
- pkc.XMLMatch.return_value = lxml.etree.Element("PrivateKey")
- self.assertItemsEqual(pkc.get_specificity(metadata),
- dict(group=metadata.group_in_category.return_value,
- prio=50))
- metadata.group_in_category.assert_called_with("foo")
-
- reset()
- pkc.XMLMatch.return_value = lxml.etree.Element("PrivateKey",
- perhost="true")
- self.assertItemsEqual(pkc.get_specificity(metadata),
- dict(host=metadata.hostname))
-
- reset()
- pkc.XMLMatch.return_value = lxml.etree.Element("PrivateKey",
- category="bar")
- self.assertItemsEqual(pkc.get_specificity(metadata),
- dict(group=metadata.group_in_category.return_value,
- prio=50))
- metadata.group_in_category.assert_called_with("bar")
-
- reset()
- pkc.XMLMatch.return_value = lxml.etree.Element("PrivateKey",
- prio="10")
- self.assertItemsEqual(pkc.get_specificity(metadata),
- dict(group=metadata.group_in_category.return_value,
- prio=10))
- metadata.group_in_category.assert_called_with("foo")
-
- reset()
- pkc.XMLMatch.return_value = lxml.etree.Element("PrivateKey")
- metadata.group_in_category.return_value = ''
- self.assertItemsEqual(pkc.get_specificity(metadata),
- dict(host=metadata.hostname))
- metadata.group_in_category.assert_called_with("foo")
-
- inner2()
-
@patch("shutil.rmtree")
@patch("%s.open" % builtins)
def test_create_data(self, mock_open, mock_rmtree):
@@ -216,7 +93,7 @@ class TestCfgPrivateKeyCreator(TestCfgCreator, TestStructFile):
# the get_specificity() return value is being used
# appropriately, we put some dummy data in it and test for
# that data
- pkc.get_specificity.side_effect = lambda m, s: dict(group="foo")
+ pkc.get_specificity.side_effect = lambda m: dict(group="foo")
pkc._gen_keypair = Mock()
privkey = os.path.join(datastore, "privkey")
pkc._gen_keypair.return_value = privkey
@@ -242,179 +119,15 @@ class TestCfgPrivateKeyCreator(TestCfgCreator, TestStructFile):
mock_open.return_value.read.side_effect = open_read_rv
reset()
- passphrase = "Bcfg2.Server.Plugins.Cfg.CfgPrivateKeyCreator.CfgPrivateKeyCreator.passphrase"
-
- @patch(passphrase, None)
- def inner():
- self.assertEqual(pkc.create_data(entry, metadata), "privatekey")
- pkc.XMLMatch.assert_called_with(metadata)
- pkc.get_specificity.assert_called_with(metadata,
- pkc.XMLMatch.return_value)
- pkc._gen_keypair.assert_called_with(metadata,
- pkc.XMLMatch.return_value)
- self.assertItemsEqual(mock_open.call_args_list,
- [call(privkey + ".pub"), call(privkey)])
- pkc.pubkey_creator.get_filename.assert_called_with(group="foo")
- pkc.pubkey_creator.write_data.assert_called_with(
- "ssh-rsa publickey pubkey.filename\n", group="foo")
- pkc.write_data.assert_called_with("privatekey", group="foo")
- mock_rmtree.assert_called_with(datastore)
-
- inner()
-
- if HAS_CRYPTO:
- @patch(passphrase, "foo")
- @patch("Bcfg2.Encryption.ssl_encrypt")
- @patch("Bcfg2.Encryption.get_algorithm")
- def inner2(mock_get_algorithm, mock_ssl_encrypt):
- reset()
- mock_ssl_encrypt.return_value = "encryptedprivatekey"
- Bcfg2.Server.Plugins.Cfg.CfgPrivateKeyCreator.HAS_CRYPTO = True
- self.assertEqual(pkc.create_data(entry, metadata),
- "encryptedprivatekey")
- pkc.XMLMatch.assert_called_with(metadata)
- pkc.get_specificity.assert_called_with(
- metadata,
- pkc.XMLMatch.return_value)
- pkc._gen_keypair.assert_called_with(metadata,
- pkc.XMLMatch.return_value)
- self.assertItemsEqual(mock_open.call_args_list,
- [call(privkey + ".pub"), call(privkey)])
- pkc.pubkey_creator.get_filename.assert_called_with(group="foo")
- pkc.pubkey_creator.write_data.assert_called_with(
- "ssh-rsa publickey pubkey.filename\n", group="foo")
- pkc.write_data.assert_called_with("encryptedprivatekey",
- group="foo", ext=".crypt")
- mock_ssl_encrypt.assert_called_with(
- "privatekey", "foo",
- algorithm=mock_get_algorithm.return_value)
- mock_rmtree.assert_called_with(datastore)
-
- inner2()
-
- def test_Index(self):
- has_crypto = Bcfg2.Server.Plugins.Cfg.CfgPrivateKeyCreator.HAS_CRYPTO
- Bcfg2.Server.Plugins.Cfg.CfgPrivateKeyCreator.HAS_CRYPTO = False
- TestStructFile.test_Index(self)
- Bcfg2.Server.Plugins.Cfg.CfgPrivateKeyCreator.HAS_CRYPTO = has_crypto
-
- @skipUnless(HAS_CRYPTO, "No crypto libraries found, skipping")
- def test_Index_crypto(self):
- Bcfg2.Server.Plugins.Cfg.CfgPrivateKeyCreator.SETUP = Mock()
- Bcfg2.Server.Plugins.Cfg.CfgPrivateKeyCreator.SETUP.cfp.get.return_value = "strict"
-
- pkc = self.get_obj()
- pkc._decrypt = Mock()
- pkc._decrypt.return_value = 'plaintext'
- pkc.data = '''
-<PrivateKey>
- <Group name="test">
- <Passphrase encrypted="foo">crypted</Passphrase>
- </Group>
- <Group name="test" negate="true">
- <Passphrase>plain</Passphrase>
- </Group>
-</PrivateKey>'''
-
- # test successful decryption
- pkc.Index()
- self.assertItemsEqual(
- pkc._decrypt.call_args_list,
- [call(el)
- for el in pkc.xdata.xpath("//Passphrase[@encrypted]")])
- for el in pkc.xdata.xpath("//Crypted"):
- self.assertEqual(el.text, pkc._decrypt.return_value)
-
- # test failed decryption, strict
- pkc._decrypt.reset_mock()
- pkc._decrypt.side_effect = EVPError
- self.assertRaises(PluginExecutionError, pkc.Index)
-
- # test failed decryption, lax
- Bcfg2.Server.Plugins.Cfg.CfgPrivateKeyCreator.SETUP.cfp.get.return_value = "lax"
- pkc._decrypt.reset_mock()
- pkc.Index()
- self.assertItemsEqual(
- pkc._decrypt.call_args_list,
- [call(el)
- for el in pkc.xdata.xpath("//Passphrase[@encrypted]")])
-
- @skipUnless(HAS_CRYPTO, "No crypto libraries found, skipping")
- @patchIf(HAS_CRYPTO, "Bcfg2.Encryption.ssl_decrypt")
- @patchIf(HAS_CRYPTO, "Bcfg2.Encryption.get_algorithm")
- @patchIf(HAS_CRYPTO, "Bcfg2.Encryption.get_passphrases")
- @patchIf(HAS_CRYPTO, "Bcfg2.Encryption.bruteforce_decrypt")
- def test_decrypt(self, mock_bruteforce, mock_get_passphrases,
- mock_get_algorithm, mock_ssl):
- pkc = self.get_obj()
- Bcfg2.Server.Plugins.Cfg.CfgPrivateKeyCreator.SETUP = MagicMock()
-
- def reset():
- mock_bruteforce.reset_mock()
- mock_get_algorithm.reset_mock()
- mock_get_passphrases.reset_mock()
- mock_ssl.reset_mock()
-
- # test element without text contents
- self.assertIsNone(pkc._decrypt(lxml.etree.Element("Test")))
- self.assertFalse(mock_bruteforce.called)
- self.assertFalse(mock_get_passphrases.called)
- self.assertFalse(mock_ssl.called)
-
- # test element with a passphrase in the config file
- reset()
- el = lxml.etree.Element("Test", encrypted="foo")
- el.text = "crypted"
- mock_get_passphrases.return_value = dict(foo="foopass",
- bar="barpass")
- mock_get_algorithm.return_value = "bf_cbc"
- mock_ssl.return_value = "decrypted with ssl"
- self.assertEqual(pkc._decrypt(el), mock_ssl.return_value)
- mock_get_passphrases.assert_called_with(
- Bcfg2.Server.Plugins.Cfg.CfgPrivateKeyCreator.SETUP)
- mock_get_algorithm.assert_called_with(
- Bcfg2.Server.Plugins.Cfg.CfgPrivateKeyCreator.SETUP)
- mock_ssl.assert_called_with(el.text, "foopass",
- algorithm="bf_cbc")
- self.assertFalse(mock_bruteforce.called)
-
- # test failure to decrypt element with a passphrase in the config
- reset()
- mock_ssl.side_effect = EVPError
- self.assertRaises(EVPError, pkc._decrypt, el)
- mock_get_passphrases.assert_called_with(
- Bcfg2.Server.Plugins.Cfg.CfgPrivateKeyCreator.SETUP)
- mock_get_algorithm.assert_called_with(
- Bcfg2.Server.Plugins.Cfg.CfgPrivateKeyCreator.SETUP)
- mock_ssl.assert_called_with(el.text, "foopass",
- algorithm="bf_cbc")
- self.assertFalse(mock_bruteforce.called)
-
- # test element without valid passphrase
- reset()
- el.set("encrypted", "true")
- mock_bruteforce.return_value = "decrypted with bruteforce"
- self.assertEqual(pkc._decrypt(el), mock_bruteforce.return_value)
- mock_get_passphrases.assert_called_with(
- Bcfg2.Server.Plugins.Cfg.CfgPrivateKeyCreator.SETUP)
- mock_get_algorithm.assert_called_with(
- Bcfg2.Server.Plugins.Cfg.CfgPrivateKeyCreator.SETUP)
- mock_bruteforce.assert_called_with(el.text,
- passphrases=["foopass",
- "barpass"],
- algorithm="bf_cbc")
- self.assertFalse(mock_ssl.called)
-
- # test failure to decrypt element without valid passphrase
- reset()
- mock_bruteforce.side_effect = EVPError
- self.assertRaises(EVPError, pkc._decrypt, el)
- mock_get_passphrases.assert_called_with(
- Bcfg2.Server.Plugins.Cfg.CfgPrivateKeyCreator.SETUP)
- mock_get_algorithm.assert_called_with(
- Bcfg2.Server.Plugins.Cfg.CfgPrivateKeyCreator.SETUP)
- mock_bruteforce.assert_called_with(el.text,
- passphrases=["foopass",
- "barpass"],
- algorithm="bf_cbc")
- self.assertFalse(mock_ssl.called)
+ self.assertEqual(pkc.create_data(entry, metadata), "privatekey")
+ pkc.XMLMatch.assert_called_with(metadata)
+ pkc.get_specificity.assert_called_with(metadata)
+ pkc._gen_keypair.assert_called_with(metadata,
+ pkc.XMLMatch.return_value)
+ self.assertItemsEqual(mock_open.call_args_list,
+ [call(privkey + ".pub"), call(privkey)])
+ pkc.pubkey_creator.get_filename.assert_called_with(group="foo")
+ pkc.pubkey_creator.write_data.assert_called_with(
+ "ssh-rsa publickey pubkey.filename\n", group="foo")
+ pkc.write_data.assert_called_with("privatekey", group="foo")
+ mock_rmtree.assert_called_with(datastore)
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgPublicKeyCreator.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgPublicKeyCreator.py
index ef4610fae..f512a6803 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgPublicKeyCreator.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgPublicKeyCreator.py
@@ -25,8 +25,8 @@ class TestCfgPublicKeyCreator(TestCfgCreator, TestStructFile):
test_obj = CfgPublicKeyCreator
should_monitor = False
+ @patch("Bcfg2.Server.Plugins.Cfg.CfgPublicKeyCreator.get_cfg", Mock())
def get_obj(self, name=None, fam=None):
- Bcfg2.Server.Plugins.Cfg.CfgPublicKeyCreator.CFG = Mock()
return TestCfgCreator.get_obj(self, name=name)
@patch("Bcfg2.Server.Plugins.Cfg.CfgCreator.handle_event")
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/Test_init.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/Test_init.py
index fdfb3a9f7..1b55beded 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/Test_init.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/Test_init.py
@@ -3,7 +3,7 @@ import sys
import errno
import lxml.etree
import Bcfg2.Options
-from Bcfg2.Compat import walk_packages
+from Bcfg2.Compat import walk_packages, ConfigParser
from mock import Mock, MagicMock, patch
from Bcfg2.Server.Plugins.Cfg import *
from Bcfg2.Server.Plugin import PluginExecutionError, Specificity
@@ -19,7 +19,7 @@ while path != "/":
path = os.path.dirname(path)
from common import *
from TestPlugin import TestSpecificData, TestEntrySet, TestGroupSpool, \
- TestPullTarget
+ TestPullTarget, TestStructFile
class TestCfgBaseFileMatcher(TestSpecificData):
@@ -152,28 +152,13 @@ class TestCfgInfo(TestCfgBaseFileMatcher):
@patch("Bcfg2.Server.Plugins.Cfg.CfgBaseFileMatcher.__init__")
def test__init(self, mock__init):
ci = self.get_obj("test.txt")
- mock__init.assert_called_with(ci, "test.txt", None, None)
+ mock__init.assert_called_with(ci, "test.txt", None)
def test_bind_info_to_entry(self):
ci = self.get_obj()
self.assertRaises(NotImplementedError,
ci.bind_info_to_entry, Mock(), Mock())
- def test__set_info(self):
- ci = self.get_obj()
- entry = Mock()
- entry.attrib = dict()
-
- info = {"foo": "foo",
- "_bar": "bar",
- "bar:baz=quux": "quux",
- "baz__": "baz",
- "__quux": "quux"}
- ci._set_info(entry, info)
- self.assertItemsEqual(entry.attrib,
- dict([(k, v) for k, v in info.items()
- if not k.startswith("__")]))
-
class TestCfgVerifier(TestCfgBaseFileMatcher):
test_obj = CfgVerifier
@@ -187,6 +172,12 @@ class TestCfgVerifier(TestCfgBaseFileMatcher):
class TestCfgCreator(TestCfgBaseFileMatcher):
test_obj = CfgCreator
path = "/foo/bar/test.txt"
+ should_monitor = False
+
+ def setUp(self):
+ TestCfgBaseFileMatcher.setUp(self)
+ set_setup_default("filemonitor", MagicMock())
+ set_setup_default("cfg_passphrase", None)
def get_obj(self, name=None):
if name is None:
@@ -256,62 +247,122 @@ class TestCfgCreator(TestCfgBaseFileMatcher):
self.assertRaises(CfgCreationError, cc.write_data, data)
+class TestXMLCfgCreator(TestCfgCreator, TestStructFile):
+ test_obj = XMLCfgCreator
+
+ def setUp(self):
+ TestCfgCreator.setUp(self)
+ TestStructFile.setUp(self)
+
+ @patch("Bcfg2.Server.Plugins.Cfg.CfgCreator.handle_event")
+ @patch("Bcfg2.Server.Plugin.helpers.StructFile.HandleEvent")
+ def test_handle_event(self, mock_HandleEvent, mock_handle_event):
+ cc = self.get_obj()
+ evt = Mock()
+ cc.handle_event(evt)
+ mock_HandleEvent.assert_called_with(cc, evt)
+ mock_handle_event.assert_called_with(cc, evt)
+
+ def test_get_specificity(self):
+ cc = self.get_obj()
+ metadata = Mock()
+
+ def reset():
+ metadata.group_in_category.reset_mock()
+
+ category = "%s.%s.category" % (self.test_obj.__module__,
+ self.test_obj.__name__)
+ @patch(category, None)
+ def inner():
+ cc.xdata = lxml.etree.Element("PrivateKey")
+ self.assertItemsEqual(cc.get_specificity(metadata),
+ dict(host=metadata.hostname))
+ inner()
+
+ @patch(category, "foo")
+ def inner2():
+ cc.xdata = lxml.etree.Element("PrivateKey")
+ self.assertItemsEqual(cc.get_specificity(metadata),
+ dict(group=metadata.group_in_category.return_value,
+ prio=50))
+ metadata.group_in_category.assert_called_with("foo")
+
+ reset()
+ cc.xdata = lxml.etree.Element("PrivateKey", perhost="true")
+ self.assertItemsEqual(cc.get_specificity(metadata),
+ dict(host=metadata.hostname))
+
+ reset()
+ cc.xdata = lxml.etree.Element("PrivateKey", category="bar")
+ self.assertItemsEqual(cc.get_specificity(metadata),
+ dict(group=metadata.group_in_category.return_value,
+ prio=50))
+ metadata.group_in_category.assert_called_with("bar")
+
+ reset()
+ cc.xdata = lxml.etree.Element("PrivateKey", prio="10")
+ self.assertItemsEqual(cc.get_specificity(metadata),
+ dict(group=metadata.group_in_category.return_value,
+ prio=10))
+ metadata.group_in_category.assert_called_with("foo")
+
+ reset()
+ cc.xdata = lxml.etree.Element("PrivateKey")
+ metadata.group_in_category.return_value = ''
+ self.assertItemsEqual(cc.get_specificity(metadata),
+ dict(host=metadata.hostname))
+ metadata.group_in_category.assert_called_with("foo")
+
+ inner2()
+
+
class TestCfgDefaultInfo(TestCfgInfo):
test_obj = CfgDefaultInfo
- def get_obj(self, defaults=None):
- if defaults is None:
- defaults = dict()
- return self.test_obj(defaults)
+ def get_obj(self, *_):
+ return self.test_obj()
- @patch("Bcfg2.Server.Plugins.Cfg.CfgInfo.__init__")
- def test__init(self, mock__init):
- defaults = Mock()
- cdi = self.get_obj(defaults=defaults)
- mock__init.assert_called_with(cdi, '')
- self.assertEqual(defaults, cdi.defaults)
+ def test__init(self):
+ pass
def test_handle_event(self):
# this CfgInfo handler doesn't handle any events -- it's not
# file-driven, but based on the built-in defaults
pass
- def test_bind_info_to_entry(self):
+ @patch("Bcfg2.Server.Plugin.default_path_metadata")
+ def test_bind_info_to_entry(self, mock_default_path_metadata):
cdi = self.get_obj()
- cdi._set_info = Mock()
- entry = Mock()
+ entry = lxml.etree.Element("Test", name="test")
+ mock_default_path_metadata.return_value = \
+ dict(owner="root", mode="0600")
cdi.bind_info_to_entry(entry, Mock())
- cdi._set_info.assert_called_with(entry, cdi.defaults)
+ self.assertItemsEqual(entry.attrib,
+ dict(owner="root", mode="0600", name="test"))
class TestCfgEntrySet(TestEntrySet):
test_obj = CfgEntrySet
+ def setUp(self):
+ TestEntrySet.setUp(self)
+ set_setup_default("cfg_validation", False)
+ set_setup_default("cfg_handlers", [])
+
def test__init(self):
pass
- def test_handlers(self):
- # this is really really difficult to mock out, so we just get
- # a list of handlers and make sure that it roughly matches
- # what's on the filesystem
- expected = []
- for submodule in walk_packages(path=Bcfg2.Server.Plugins.Cfg.__path__,
- prefix="Bcfg2.Server.Plugins.Cfg."):
- expected.append(submodule[1].rsplit('.', 1)[-1])
- self.assertItemsEqual(expected, [h.__name__ for h in handlers()])
-
- @patch("Bcfg2.Server.Plugins.Cfg.handlers")
- def test_handle_event(self, mock_handlers):
+ def test_handle_event(self):
eset = self.get_obj()
eset.entry_init = Mock()
- mock_handlers.return_value = [Mock(), Mock(), Mock()]
- for hdlr in mock_handlers.return_value:
+ Bcfg2.Options.setup.cfg_handlers = [Mock(), Mock(), Mock()]
+ for hdlr in Bcfg2.Options.setup.cfg_handlers:
hdlr.__name__ = "handler"
eset.entries = dict()
def reset():
eset.entry_init.reset_mock()
- for hdlr in mock_handlers.return_value:
+ for hdlr in Bcfg2.Options.setup.cfg_handlers:
hdlr.reset_mock()
# test that a bogus deleted event is discarded
@@ -321,18 +372,19 @@ class TestCfgEntrySet(TestEntrySet):
eset.handle_event(evt)
self.assertFalse(eset.entry_init.called)
self.assertItemsEqual(eset.entries, dict())
- for hdlr in mock_handlers.return_value:
+ for hdlr in Bcfg2.Options.setup.cfg_handlers:
self.assertFalse(hdlr.handles.called)
self.assertFalse(hdlr.ignore.called)
# test creation of a new file
for action in ["exists", "created", "changed"]:
+ print("Testing handling of %s events" % action)
evt = Mock()
evt.code2str.return_value = action
evt.filename = os.path.join(datastore, "test.txt")
# test with no handler that handles
- for hdlr in mock_handlers.return_value:
+ for hdlr in Bcfg2.Options.setup.cfg_handlers:
hdlr.handles.return_value = False
hdlr.ignore.return_value = False
@@ -340,16 +392,16 @@ class TestCfgEntrySet(TestEntrySet):
eset.handle_event(evt)
self.assertFalse(eset.entry_init.called)
self.assertItemsEqual(eset.entries, dict())
- for hdlr in mock_handlers.return_value:
+ for hdlr in Bcfg2.Options.setup.cfg_handlers:
hdlr.handles.assert_called_with(evt, basename=eset.path)
hdlr.ignore.assert_called_with(evt, basename=eset.path)
# test with a handler that handles the entry
reset()
- mock_handlers.return_value[-1].handles.return_value = True
+ Bcfg2.Options.setup.cfg_handlers[-1].handles.return_value = True
eset.handle_event(evt)
- eset.entry_init.assert_called_with(evt, mock_handlers.return_value[-1])
- for hdlr in mock_handlers.return_value:
+ eset.entry_init.assert_called_with(evt, Bcfg2.Options.setup.cfg_handlers[-1])
+ for hdlr in Bcfg2.Options.setup.cfg_handlers:
hdlr.handles.assert_called_with(evt, basename=eset.path)
if not hdlr.return_value:
hdlr.ignore.assert_called_with(evt, basename=eset.path)
@@ -357,14 +409,14 @@ class TestCfgEntrySet(TestEntrySet):
# test with a handler that ignores the entry before one
# that handles it
reset()
- mock_handlers.return_value[0].ignore.return_value = True
+ Bcfg2.Options.setup.cfg_handlers[0].ignore.return_value = True
eset.handle_event(evt)
self.assertFalse(eset.entry_init.called)
- mock_handlers.return_value[0].handles.assert_called_with(evt,
- basename=eset.path)
- mock_handlers.return_value[0].ignore.assert_called_with(evt,
- basename=eset.path)
- for hdlr in mock_handlers.return_value[1:]:
+ Bcfg2.Options.setup.cfg_handlers[0].handles.assert_called_with(
+ evt, basename=eset.path)
+ Bcfg2.Options.setup.cfg_handlers[0].ignore.assert_called_with(
+ evt, basename=eset.path)
+ for hdlr in Bcfg2.Options.setup.cfg_handlers[1:]:
self.assertFalse(hdlr.handles.called)
self.assertFalse(hdlr.ignore.called)
@@ -376,7 +428,7 @@ class TestCfgEntrySet(TestEntrySet):
eset.entries[evt.filename] = Mock()
eset.handle_event(evt)
self.assertFalse(eset.entry_init.called)
- for hdlr in mock_handlers.return_value:
+ for hdlr in Bcfg2.Options.setup.cfg_handlers:
self.assertFalse(hdlr.handles.called)
self.assertFalse(hdlr.ignore.called)
eset.entries[evt.filename].handle_event.assert_called_with(evt)
@@ -386,7 +438,7 @@ class TestCfgEntrySet(TestEntrySet):
evt.code2str.return_value = "deleted"
eset.handle_event(evt)
self.assertFalse(eset.entry_init.called)
- for hdlr in mock_handlers.return_value:
+ for hdlr in Bcfg2.Options.setup.cfg_handlers:
self.assertFalse(hdlr.handles.called)
self.assertFalse(hdlr.ignore.called)
self.assertItemsEqual(eset.entries, dict())
@@ -438,15 +490,15 @@ class TestCfgEntrySet(TestEntrySet):
@patch("Bcfg2.Server.Plugins.Cfg.u_str")
@patch("Bcfg2.Server.Plugins.Cfg.b64encode")
def test_bind_entry(self, mock_b64encode, mock_u_str):
- Bcfg2.Server.Plugins.Cfg.SETUP = dict(validate=False)
-
mock_u_str.side_effect = lambda x: x
+ Bcfg2.Options.setup.cfg_validation = False
eset = self.get_obj()
eset.bind_info_to_entry = Mock()
eset._generate_data = Mock()
eset.get_handlers = Mock()
eset._validate_data = Mock()
+ eset.setup = dict(validate=False)
def reset():
mock_b64encode.reset_mock()
@@ -524,7 +576,7 @@ class TestCfgEntrySet(TestEntrySet):
# test successful validation
entry = reset()
- Bcfg2.Server.Plugins.Cfg.SETUP['validate'] = True
+ Bcfg2.Options.setup.cfg_validation = True
bound = eset.bind_entry(entry, metadata)
eset.bind_info_to_entry.assert_called_with(entry, metadata)
eset._generate_data.assert_called_with(entry, metadata)
@@ -546,16 +598,16 @@ class TestCfgEntrySet(TestEntrySet):
def test_get_handlers(self):
eset = self.get_obj()
eset.entries['test1.txt'] = CfgInfo("test1.txt")
- eset.entries['test2.txt'] = CfgGenerator("test2.txt", Mock(), None)
+ eset.entries['test2.txt'] = CfgGenerator("test2.txt", Mock())
eset.entries['test2.txt'].specific.matches.return_value = True
eset.entries['test3.txt'] = CfgInfo("test3.txt")
- eset.entries['test4.txt'] = CfgGenerator("test4.txt", Mock(), None)
+ eset.entries['test4.txt'] = CfgGenerator("test4.txt", Mock())
eset.entries['test4.txt'].specific.matches.return_value = False
- eset.entries['test5.txt'] = CfgGenerator("test5.txt", Mock(), None)
+ eset.entries['test5.txt'] = CfgGenerator("test5.txt", Mock())
eset.entries['test5.txt'].specific.matches.return_value = True
- eset.entries['test6.txt'] = CfgVerifier("test6.txt", Mock(), None)
+ eset.entries['test6.txt'] = CfgVerifier("test6.txt", Mock())
eset.entries['test6.txt'].specific.matches.return_value = True
- eset.entries['test7.txt'] = CfgFilter("test7.txt", Mock(), None)
+ eset.entries['test7.txt'] = CfgFilter("test7.txt", Mock())
eset.entries['test7.txt'].specific.matches.return_value = False
def reset():
@@ -603,24 +655,24 @@ class TestCfgEntrySet(TestEntrySet):
if hasattr(entry.specific.matches, "called"):
self.assertFalse(entry.specific.matches.called)
- def test_bind_info_to_entry(self):
- default_info = Bcfg2.Server.Plugins.Cfg.DEFAULT_INFO
+ @patch("Bcfg2.Server.Plugins.Cfg.CfgDefaultInfo")
+ def test_bind_info_to_entry(self, mock_DefaultInfo):
eset = self.get_obj()
eset.get_handlers = Mock()
eset.get_handlers.return_value = []
- Bcfg2.Server.Plugins.Cfg.DEFAULT_INFO = Mock()
metadata = Mock()
def reset():
eset.get_handlers.reset_mock()
- Bcfg2.Server.Plugins.Cfg.DEFAULT_INFO.reset_mock()
+ mock_DefaultInfo.reset_mock()
return lxml.etree.Element("Path", name="/test.txt")
# test with no info handlers
entry = reset()
eset.bind_info_to_entry(entry, metadata)
eset.get_handlers.assert_called_with(metadata, CfgInfo)
- Bcfg2.Server.Plugins.Cfg.DEFAULT_INFO.bind_info_to_entry.assert_called_with(entry, metadata)
+ mock_DefaultInfo.return_value.bind_info_to_entry.assert_called_with(
+ entry, metadata)
self.assertEqual(entry.get("type"), "file")
# test with one info handler
@@ -629,7 +681,8 @@ class TestCfgEntrySet(TestEntrySet):
eset.get_handlers.return_value = [handler]
eset.bind_info_to_entry(entry, metadata)
eset.get_handlers.assert_called_with(metadata, CfgInfo)
- Bcfg2.Server.Plugins.Cfg.DEFAULT_INFO.bind_info_to_entry.assert_called_with(entry, metadata)
+ mock_DefaultInfo.return_value.bind_info_to_entry.assert_called_with(
+ entry, metadata)
handler.bind_info_to_entry.assert_called_with(entry, metadata)
self.assertEqual(entry.get("type"), "file")
@@ -639,7 +692,8 @@ class TestCfgEntrySet(TestEntrySet):
eset.get_handlers.return_value = handlers
eset.bind_info_to_entry(entry, metadata)
eset.get_handlers.assert_called_with(metadata, CfgInfo)
- Bcfg2.Server.Plugins.Cfg.DEFAULT_INFO.bind_info_to_entry.assert_called_with(entry, metadata)
+ mock_DefaultInfo.return_value.bind_info_to_entry.assert_called_with(
+ entry, metadata)
# we don't care which handler gets called as long as exactly
# one of them does
called = 0
@@ -650,8 +704,6 @@ class TestCfgEntrySet(TestEntrySet):
self.assertEqual(called, 1)
self.assertEqual(entry.get("type"), "file")
- Bcfg2.Server.Plugins.Cfg.DEFAULT_INFO = default_info
-
def test_create_data(self):
eset = self.get_obj()
eset.best_matching = Mock()
@@ -753,35 +805,16 @@ class TestCfgEntrySet(TestEntrySet):
class TestCfg(TestGroupSpool, TestPullTarget):
test_obj = Cfg
+ def setUp(self):
+ TestGroupSpool.setUp(self)
+ TestPullTarget.setUp(self)
+ set_setup_default("cfg_handlers", [])
+
def get_obj(self, core=None):
if core is None:
core = Mock()
- core.setup = MagicMock()
return TestGroupSpool.get_obj(self, core=core)
- @patch("Bcfg2.Server.Plugin.GroupSpool.__init__")
- @patch("Bcfg2.Server.Plugin.PullTarget.__init__")
- def test__init(self, mock_pulltarget_init, mock_groupspool_init):
- core = Mock()
- core.setup = MagicMock()
- cfg = self.test_obj(core, datastore)
- mock_pulltarget_init.assert_called_with(cfg)
- mock_groupspool_init.assert_called_with(cfg, core, datastore)
- core.setup.add_option.assert_called_with("validate",
- Bcfg2.Options.CFG_VALIDATION)
- core.setup.reparse.assert_called_with()
-
- core.reset_mock()
- core.setup.reset_mock()
- mock_pulltarget_init.reset_mock()
- mock_groupspool_init.reset_mock()
- core.setup.__contains__.return_value = True
- cfg = self.test_obj(core, datastore)
- mock_pulltarget_init.assert_called_with(cfg)
- mock_groupspool_init.assert_called_with(cfg, core, datastore)
- self.assertFalse(core.setup.add_option.called)
- self.assertFalse(core.setup.reparse.called)
-
def test_has_generator(self):
cfg = self.get_obj()
cfg.entries = dict()
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestDecisions.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestDecisions.py
new file mode 100644
index 000000000..537ceb4ff
--- /dev/null
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestDecisions.py
@@ -0,0 +1,60 @@
+import os
+import sys
+import lxml.etree
+from mock import Mock, MagicMock, patch
+from Bcfg2.Server.Plugins.Decisions import *
+
+# 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)
+from common import *
+from TestPlugin import TestStructFile, TestPlugin, TestDecision
+
+
+class TestDecisionFile(TestStructFile):
+ test_obj = DecisionFile
+
+ def test_get_decisions(self):
+ df = self.get_obj()
+ metadata = Mock()
+
+ df.xdata = None
+ self.assertItemsEqual(df.get_decisions(metadata), [])
+
+ df.xdata = lxml.etree.Element("Decisions")
+ df.XMLMatch = Mock()
+ df.XMLMatch.return_value = lxml.etree.Element("Decisions")
+ lxml.etree.SubElement(df.XMLMatch.return_value,
+ "Decision", type="Service", name='*')
+ lxml.etree.SubElement(df.XMLMatch.return_value,
+ "Decision", type="Path",
+ name='/etc/apt/apt.conf')
+
+ self.assertItemsEqual(df.get_decisions(metadata),
+ [("Service", '*'),
+ ("Path", '/etc/apt/apt.conf')])
+ df.XMLMatch.assert_called_with(metadata)
+
+
+class TestDecisions(TestPlugin, TestDecision):
+ test_obj = Decisions
+
+ def test_GetDecisions(self):
+ d = self.get_obj()
+ d.whitelist = Mock()
+ d.blacklist = Mock()
+ metadata = Mock()
+
+ self.assertEqual(d.GetDecisions(metadata, "whitelist"),
+ d.whitelist.get_decision.return_value)
+ d.whitelist.get_decision.assert_called_with(metadata)
+
+ self.assertEqual(d.GetDecisions(metadata, "blacklist"),
+ d.blacklist.get_decision.return_value)
+ d.blacklist.get_decision.assert_called_with(metadata)
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestDefaults.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestDefaults.py
index 7be3d8e84..9b4a6af88 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestDefaults.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestDefaults.py
@@ -1,5 +1,6 @@
import os
import sys
+import copy
import lxml.etree
from mock import Mock, MagicMock, patch
from Bcfg2.Server.Plugins.Defaults import *
@@ -62,3 +63,31 @@ class TestDefaults(TestRules, TestGoalValidator):
def test__regex_enabled(self):
r = self.get_obj()
self.assertTrue(r._regex_enabled)
+
+ def _do_test(self, name, groups=None):
+ if groups is None:
+ groups = []
+ d = self.get_obj()
+ metadata = Mock(groups=groups)
+ config = lxml.etree.Element("Configuration")
+ struct = lxml.etree.SubElement(config, "Bundle", name=name)
+ entry = copy.deepcopy(self.abstract[name])
+ struct.append(entry)
+ d.validate_goals(metadata, config)
+ self.assertXMLEqual(entry, self.concrete[name])
+
+ def _do_test_failure(self, name, groups=None, handles=None):
+ if groups is None:
+ groups = []
+ d = self.get_obj()
+ metadata = Mock(groups=groups)
+ config = lxml.etree.Element("Configuration")
+ struct = lxml.etree.SubElement(config, "Bundle", name=name)
+ orig = copy.deepcopy(self.abstract[name])
+ entry = copy.deepcopy(self.abstract[name])
+ struct.append(entry)
+ d.validate_goals(metadata, config)
+ self.assertXMLEqual(entry, orig)
+
+ def test_regex(self):
+ self._do_test('regex')
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestMetadata.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestMetadata.py
index a07fffba1..d3fa15236 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestMetadata.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestMetadata.py
@@ -6,7 +6,6 @@ import socket
import lxml.etree
import Bcfg2.Server
import Bcfg2.Server.Plugin
-from Bcfg2.Server.Plugins.Metadata import *
from mock import Mock, MagicMock, patch
# add all parent testsuite directories to sys.path to allow (most)
@@ -19,9 +18,13 @@ while path != "/":
break
path = os.path.dirname(path)
from common import *
+from Bcfg2.Server.Plugins.Metadata import load_django_models
from TestPlugin import TestXMLFileBacked, TestMetadata as _TestMetadata, \
TestClientRunHooks, TestDatabaseBacked
+load_django_models()
+from Bcfg2.Server.Plugins.Metadata import *
+
def get_clients_test_tree():
return lxml.etree.XML('''
@@ -88,18 +91,18 @@ def get_groups_test_tree():
</Groups>''').getroottree()
-def get_metadata_object(core=None, watch_clients=False, use_db=False):
+def get_metadata_object(core=None):
if core is None:
core = Mock()
- core.setup = MagicMock()
core.metadata_cache = MagicMock()
- core.setup.cfp.getboolean = Mock(return_value=use_db)
+ set_setup_default("password")
@patchIf(not isinstance(os.makedirs, Mock), "os.makedirs", Mock())
@patchIf(not isinstance(lxml.etree.Element, Mock),
"lxml.etree.Element", Mock())
+
def inner():
- return Metadata(core, datastore, watch_clients=watch_clients)
+ return Metadata(core)
return inner()
@@ -108,117 +111,117 @@ class TestMetadataDB(DBModelTestCase):
models = [MetadataClientModel]
-if HAS_DJANGO or can_skip:
- class TestClientVersions(TestDatabaseBacked):
- test_clients = dict(client1="1.2.0",
- client2="1.2.2",
- client3="1.3.0pre1",
- client4="1.1.0",
- client5=None,
- client6=None)
-
- @skipUnless(HAS_DJANGO, "Django not found")
- def setUp(self):
- self.test_obj = ClientVersions
- syncdb(TestMetadataDB)
- for client, version in self.test_clients.items():
- MetadataClientModel(hostname=client, version=version).save()
-
- def test__contains(self):
- v = self.get_obj()
- self.assertIn("client1", v)
- self.assertIn("client5", v)
- self.assertNotIn("client__contains", v)
-
- def test_keys(self):
- v = self.get_obj()
- self.assertItemsEqual(self.test_clients.keys(), v.keys())
-
- def test__setitem(self):
- v = self.get_obj()
-
- # test setting version of existing client
- v["client1"] = "1.2.3"
- self.assertIn("client1", v)
- self.assertEqual(v['client1'], "1.2.3")
- client = MetadataClientModel.objects.get(hostname="client1")
- self.assertEqual(client.version, "1.2.3")
-
- # test adding new client
- new = "client__setitem"
- v[new] = "1.3.0"
- self.assertIn(new, v)
- self.assertEqual(v[new], "1.3.0")
- client = MetadataClientModel.objects.get(hostname=new)
- self.assertEqual(client.version, "1.3.0")
-
- # test adding new client with no version
- new2 = "client__setitem_2"
- v[new2] = None
- self.assertIn(new2, v)
- self.assertEqual(v[new2], None)
- client = MetadataClientModel.objects.get(hostname=new2)
- self.assertEqual(client.version, None)
-
- def test__getitem(self):
- v = self.get_obj()
-
- # test getting existing client
- self.assertEqual(v['client2'], "1.2.2")
- self.assertIsNone(v['client5'])
-
- # test exception on nonexistent client
- expected = KeyError
- try:
- v['clients__getitem']
- except expected:
- pass
- except:
- err = sys.exc_info()[1]
- self.assertFalse(True, "%s raised instead of %s" %
- (err.__class__.__name__,
- expected.__class__.__name__))
- else:
- self.assertFalse(True,
- "%s not raised" % expected.__class__.__name__)
+class TestClientVersions(TestDatabaseBacked):
+ test_clients = dict(client1="1.2.0",
+ client2="1.2.2",
+ client3="1.3.0pre1",
+ client4="1.1.0",
+ client5=None,
+ client6=None)
+
+ @skipUnless(HAS_DJANGO, "Django not found")
+ def setUp(self):
+ TestDatabaseBacked.setUp(self)
+ self.test_obj = ClientVersions
+ syncdb(TestMetadataDB)
+ for client, version in self.test_clients.items():
+ MetadataClientModel(hostname=client, version=version).save()
+
+ def test__contains(self):
+ v = self.get_obj()
+ self.assertIn("client1", v)
+ self.assertIn("client5", v)
+ self.assertNotIn("client__contains", v)
+
+ def test_keys(self):
+ v = self.get_obj()
+ self.assertItemsEqual(self.test_clients.keys(), v.keys())
+
+ def test__setitem(self):
+ v = self.get_obj()
+
+ # test setting version of existing client
+ v["client1"] = "1.2.3"
+ self.assertIn("client1", v)
+ self.assertEqual(v['client1'], "1.2.3")
+ client = MetadataClientModel.objects.get(hostname="client1")
+ self.assertEqual(client.version, "1.2.3")
+
+ # test adding new client
+ new = "client__setitem"
+ v[new] = "1.3.0"
+ self.assertIn(new, v)
+ self.assertEqual(v[new], "1.3.0")
+ client = MetadataClientModel.objects.get(hostname=new)
+ self.assertEqual(client.version, "1.3.0")
+
+ # test adding new client with no version
+ new2 = "client__setitem_2"
+ v[new2] = None
+ self.assertIn(new2, v)
+ self.assertEqual(v[new2], None)
+ client = MetadataClientModel.objects.get(hostname=new2)
+ self.assertEqual(client.version, None)
+
+ def test__getitem(self):
+ v = self.get_obj()
+
+ # test getting existing client
+ self.assertEqual(v['client2'], "1.2.2")
+ self.assertIsNone(v['client5'])
+
+ # test exception on nonexistent client
+ expected = KeyError
+ try:
+ v['clients__getitem']
+ except expected:
+ pass
+ except:
+ err = sys.exc_info()[1]
+ self.assertFalse(True, "%s raised instead of %s" %
+ (err.__class__.__name__,
+ expected.__class__.__name__))
+ else:
+ self.assertFalse(True,
+ "%s not raised" % expected.__class__.__name__)
- def test__len(self):
- v = self.get_obj()
- self.assertEqual(len(v), MetadataClientModel.objects.count())
+ def test__len(self):
+ v = self.get_obj()
+ self.assertEqual(len(v), MetadataClientModel.objects.count())
- def test__iter(self):
- v = self.get_obj()
- self.assertItemsEqual([h for h in iter(v)], v.keys())
+ def test__iter(self):
+ v = self.get_obj()
+ self.assertItemsEqual([h for h in iter(v)], v.keys())
- def test__delitem(self):
- v = self.get_obj()
+ def test__delitem(self):
+ v = self.get_obj()
- # test adding new client
- new = "client__delitem"
- v[new] = "1.3.0"
+ # test adding new client
+ new = "client__delitem"
+ v[new] = "1.3.0"
- del v[new]
- self.assertIn(new, v)
- self.assertIsNone(v[new])
+ del v[new]
+ self.assertIn(new, v)
+ self.assertIsNone(v[new])
class TestXMLMetadataConfig(TestXMLFileBacked):
test_obj = XMLMetadataConfig
path = os.path.join(datastore, 'Metadata', 'clients.xml')
- def get_obj(self, basefile="clients.xml", core=None, watch_clients=False):
- self.metadata = get_metadata_object(core=core,
- watch_clients=watch_clients)
+ def get_obj(self, basefile="clients.xml", core=None):
+ self.metadata = get_metadata_object(core=core)
@patchIf(not isinstance(lxml.etree.Element, Mock),
"lxml.etree.Element", Mock())
def inner():
- return XMLMetadataConfig(self.metadata, watch_clients, basefile)
+ return XMLMetadataConfig(self.metadata, basefile)
return inner()
+ @patch("Bcfg2.Server.FileMonitor.get_fam", Mock())
def test__init(self):
xmc = self.get_obj()
- self.assertEqual(self.metadata.core.fam, xmc.fam)
- self.assertFalse(xmc.fam.AddMonitor.called)
+ self.assertNotIn(call(xmc.basefile),
+ xmc.fam.AddMonitor.call_args_list)
def test_xdata(self):
config = self.get_obj()
@@ -262,20 +265,15 @@ class TestXMLMetadataConfig(TestXMLFileBacked):
self.assertEqual(config.base_xdata, "<test/>")
def test_add_monitor(self):
- core = MagicMock()
- config = self.get_obj(core=core)
+ config = self.get_obj()
+ config.fam = Mock()
fname = "test.xml"
fpath = os.path.join(self.metadata.data, fname)
config.extras = []
config.add_monitor(fpath)
- self.assertFalse(core.fam.AddMonitor.called)
- self.assertEqual(config.extras, [fpath])
-
- config = self.get_obj(core=core, watch_clients=True)
- config.add_monitor(fpath)
- core.fam.AddMonitor.assert_called_with(fpath, config.metadata)
+ config.fam.AddMonitor.assert_called_with(fpath, config.metadata)
self.assertItemsEqual(config.extras, [fpath])
def test_Index(self):
@@ -480,11 +478,16 @@ class TestClientMetadata(Bcfg2TestCase):
class TestMetadata(_TestMetadata, TestClientRunHooks, TestDatabaseBacked):
test_obj = Metadata
- use_db = False
- def get_obj(self, core=None, watch_clients=False):
- return get_metadata_object(core=core, watch_clients=watch_clients,
- use_db=self.use_db)
+ def setUp(self):
+ _TestMetadata.setUp(self)
+ TestClientRunHooks.setUp(self)
+ TestDatabaseBacked.setUp(self)
+ Bcfg2.Options.setup.metadata_db = False
+ Bcfg2.Options.setup.authentication = "cert+password"
+
+ def get_obj(self, core=None):
+ return get_metadata_object(core=core)
@skipUnless(HAS_DJANGO, "Django not found")
def test__use_db(self):
@@ -504,33 +507,24 @@ class TestMetadata(_TestMetadata, TestClientRunHooks, TestDatabaseBacked):
client_name = "%s%s" % (prefix, i)
return client_name
- def test__init(self):
- # test with watch_clients=False
+ @patch("Bcfg2.Server.FileMonitor.get_fam")
+ def test__init(self, mock_get_fam):
core = MagicMock()
metadata = self.get_obj(core=core)
- self.assertIsInstance(metadata, Bcfg2.Server.Plugin.Plugin)
- self.assertIsInstance(metadata, Bcfg2.Server.Plugin.Metadata)
- self.assertIsInstance(metadata, Bcfg2.Server.Plugin.ClientRunHooks)
- self.assertIsInstance(metadata.clients_xml, XMLMetadataConfig)
- self.assertIsInstance(metadata.groups_xml, XMLMetadataConfig)
- self.assertIsInstance(metadata.query, MetadataQuery)
- self.assertEqual(metadata.states, dict())
-
- # test with watch_clients=True
- core.fam = MagicMock()
- metadata = self.get_obj(core=core, watch_clients=True)
self.assertEqual(len(metadata.states), 2)
- core.fam.AddMonitor.assert_any_call(os.path.join(metadata.data,
- "groups.xml"),
- metadata)
- core.fam.AddMonitor.assert_any_call(os.path.join(metadata.data,
- "clients.xml"),
- metadata)
-
- core.fam.reset_mock()
- core.fam.AddMonitor = Mock(side_effect=IOError)
+ mock_get_fam.return_value.AddMonitor.assert_any_call(
+ os.path.join(metadata.data, "groups.xml"),
+ metadata)
+ mock_get_fam.return_value.AddMonitor.assert_any_call(
+ os.path.join(metadata.data, "clients.xml"),
+ metadata)
+
+ mock_get_fam.reset_mock()
+ fam = Mock()
+ fam.AddMonitor = Mock(side_effect=IOError)
+ mock_get_fam.return_value = fam
self.assertRaises(Bcfg2.Server.Plugin.PluginInitError,
- self.get_obj, core=core, watch_clients=True)
+ self.get_obj, core=core)
@patch('os.makedirs', Mock())
@patch('%s.open' % builtins)
@@ -591,6 +585,7 @@ class TestMetadata(_TestMetadata, TestClientRunHooks, TestDatabaseBacked):
def test_add_group(self):
metadata = self.get_obj()
metadata.groups_xml.write = Mock()
+ metadata.groups_xml.load_xml = Mock()
metadata.groups_xml.data = lxml.etree.XML('<Groups/>').getroottree()
metadata.groups_xml.basedata = copy.copy(metadata.groups_xml.data)
@@ -623,6 +618,7 @@ class TestMetadata(_TestMetadata, TestClientRunHooks, TestDatabaseBacked):
def test_update_group(self):
metadata = self.get_obj()
metadata.groups_xml.write_xml = Mock()
+ metadata.groups_xml.load_xml = Mock()
metadata.groups_xml.data = copy.deepcopy(get_groups_test_tree())
metadata.groups_xml.basedata = copy.copy(metadata.groups_xml.data)
@@ -640,6 +636,7 @@ class TestMetadata(_TestMetadata, TestClientRunHooks, TestDatabaseBacked):
def test_remove_group(self):
metadata = self.get_obj()
metadata.groups_xml.write_xml = Mock()
+ metadata.groups_xml.load_xml = Mock()
metadata.groups_xml.data = copy.deepcopy(get_groups_test_tree())
metadata.groups_xml.basedata = copy.copy(metadata.groups_xml.data)
@@ -655,6 +652,7 @@ class TestMetadata(_TestMetadata, TestClientRunHooks, TestDatabaseBacked):
def test_add_bundle(self):
metadata = self.get_obj()
metadata.groups_xml.write = Mock()
+ metadata.groups_xml.load_xml = Mock()
metadata.groups_xml.data = lxml.etree.XML('<Groups/>').getroottree()
metadata.groups_xml.basedata = copy.copy(metadata.groups_xml.data)
@@ -678,6 +676,7 @@ class TestMetadata(_TestMetadata, TestClientRunHooks, TestDatabaseBacked):
def test_remove_bundle(self):
metadata = self.get_obj()
metadata.groups_xml.write_xml = Mock()
+ metadata.groups_xml.load_xml = Mock()
metadata.groups_xml.data = copy.deepcopy(get_groups_test_tree())
metadata.groups_xml.basedata = copy.copy(metadata.groups_xml.data)
@@ -693,6 +692,7 @@ class TestMetadata(_TestMetadata, TestClientRunHooks, TestDatabaseBacked):
def test_add_client(self):
metadata = self.get_obj()
metadata.clients_xml.write = Mock()
+ metadata.clients_xml.load_xml = Mock()
metadata.clients_xml.data = lxml.etree.XML('<Clients/>').getroottree()
metadata.clients_xml.basedata = copy.copy(metadata.clients_xml.data)
@@ -727,6 +727,7 @@ class TestMetadata(_TestMetadata, TestClientRunHooks, TestDatabaseBacked):
def test_update_client(self):
metadata = self.get_obj()
metadata.clients_xml.write_xml = Mock()
+ metadata.clients_xml.load_xml = Mock()
metadata.clients_xml.data = copy.deepcopy(get_clients_test_tree())
metadata.clients_xml.basedata = copy.copy(metadata.clients_xml.data)
@@ -762,7 +763,7 @@ class TestMetadata(_TestMetadata, TestClientRunHooks, TestDatabaseBacked):
metadata.clients_xml.xdata = copy.deepcopy(get_clients_test_tree())
metadata._handle_clients_xml_event(Mock())
- if not self.use_db:
+ if not Bcfg2.Options.setup.metadata_db:
self.assertItemsEqual(metadata.clients,
dict([(c.get("name"), c.get("profile"))
for c in get_clients_test_tree().findall("//Client")]))
@@ -1251,10 +1252,13 @@ class TestMetadata(_TestMetadata, TestClientRunHooks, TestDatabaseBacked):
class TestMetadataBase(TestMetadata):
""" base test object for testing Metadata with database enabled """
__test__ = False
- use_db = True
@skipUnless(HAS_DJANGO, "Django not found")
def setUp(self):
+ _TestMetadata.setUp(self)
+ TestClientRunHooks.setUp(self)
+ TestDatabaseBacked.setUp(self)
+ Bcfg2.Options.setup.metadata_db = True
syncdb(TestMetadataDB)
def load_clients_data(self, metadata=None, xdata=None):
@@ -1274,25 +1278,24 @@ class TestMetadataBase(TestMetadata):
return client_name
@patch('os.path.exists')
- def test__init(self, mock_exists):
- core = MagicMock()
- core.fam = Mock()
+ @patch('Bcfg2.Server.FileMonitor.get_fam')
+ def test__init(self, mock_get_fam, mock_exists):
mock_exists.return_value = False
- metadata = self.get_obj(core=core, watch_clients=True)
+ metadata = self.get_obj()
self.assertIsInstance(metadata, Bcfg2.Server.Plugin.DatabaseBacked)
- core.fam.AddMonitor.assert_called_once_with(os.path.join(metadata.data,
- "groups.xml"),
- metadata)
+ mock_get_fam.return_value.AddMonitor.assert_called_with(
+ os.path.join(metadata.data, "groups.xml"),
+ metadata)
mock_exists.return_value = True
- core.fam.reset_mock()
- metadata = self.get_obj(core=core, watch_clients=True)
- core.fam.AddMonitor.assert_any_call(os.path.join(metadata.data,
- "groups.xml"),
- metadata)
- core.fam.AddMonitor.assert_any_call(os.path.join(metadata.data,
- "clients.xml"),
- metadata)
+ mock_get_fam.reset_mock()
+ metadata = self.get_obj()
+ mock_get_fam.return_value.AddMonitor.assert_any_call(
+ os.path.join(metadata.data, "groups.xml"),
+ metadata)
+ mock_get_fam.return_value.AddMonitor.assert_any_call(
+ os.path.join(metadata.data, "clients.xml"),
+ metadata)
def test_add_group(self):
pass
@@ -1356,12 +1359,7 @@ class TestMetadataBase(TestMetadata):
class TestMetadata_NoClientsXML(TestMetadataBase):
""" test Metadata without a clients.xml. we have to disable or
override tests that rely on client options """
- # only run these tests if it's possible to skip tests or if we
- # have django. otherwise they'll all get run because our fake
- # skipping decorators for python < 2.7 won't work when they
- # decorate setUp()
- if can_skip or HAS_DJANGO:
- __test__ = True
+ __test__ = True
def load_groups_data(self, metadata=None, xdata=None):
if metadata is None:
@@ -1525,17 +1523,13 @@ class TestMetadata_NoClientsXML(TestMetadataBase):
class TestMetadata_ClientsXML(TestMetadataBase):
""" test Metadata with a clients.xml. """
- # only run these tests if it's possible to skip tests or if we
- # have django. otherwise they'll all get run because our fake
- # skipping decorators for python < 2.7 won't work when they
- # decorate setUp()
- if can_skip or HAS_DJANGO:
- __test__ = True
+ __test__ = True
def load_clients_data(self, metadata=None, xdata=None):
if metadata is None:
metadata = self.get_obj()
- metadata.core.fam = Mock()
+ fam = Bcfg2.Server.FileMonitor._FAM
+ Bcfg2.Server.FileMonitor._FAM = MagicMock()
@patchIf(not isinstance(lxml.etree.Element, Mock),
"lxml.etree.Element", Mock())
def inner():
@@ -1543,5 +1537,7 @@ class TestMetadata_ClientsXML(TestMetadataBase):
inner()
metadata = TestMetadata.load_clients_data(self, metadata=metadata,
xdata=xdata)
- return TestMetadataBase.load_clients_data(self, metadata=metadata,
- xdata=xdata)
+ rv = TestMetadataBase.load_clients_data(self, metadata=metadata,
+ xdata=xdata)
+ Bcfg2.Server.FileMonitor._FAM = fam
+ return rv
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProbes.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProbes.py
index 2face023f..32766b5c1 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProbes.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProbes.py
@@ -1,8 +1,8 @@
import os
import re
import sys
-import copy
-import time
+import shutil
+import tempfile
import lxml.etree
import Bcfg2.version
import Bcfg2.Server
@@ -19,54 +19,22 @@ while path != "/":
break
path = os.path.dirname(path)
from common import *
-from Bcfg2.Server.Plugins.Probes import *
-from TestPlugin import TestEntrySet, TestProbing, TestConnector, \
+from Bcfg2.Server.Plugins.Probes import load_django_models
+from TestPlugin import TestEntrySet, TestPlugin, \
TestDatabaseBacked
-# test data for JSON and YAML tests
-test_data = dict(a=1, b=[1, 2, 3], c="test",
- d=dict(a=1, b=dict(a=1), c=(1, "2", 3)))
-
-
-class FakeElement(lxml.etree._Element):
- getroottree = Mock()
-
- def __init__(self, el):
- self._element = el
-
- def __getattribute__(self, attr):
- el = lxml.etree._Element.__getattribute__(self,
- '__dict__')['_element']
- if attr == "getroottree":
- return FakeElement.getroottree
- elif attr == "_element":
- return el
- else:
- return getattr(el, attr)
-
-
-class StoringElement(object):
- OriginalElement = copy.copy(lxml.etree.Element)
-
- def __init__(self):
- self.element = None
- self.return_value = None
-
- def __call__(self, *args, **kwargs):
- self.element = self.OriginalElement(*args, **kwargs)
- self.return_value = FakeElement(self.element)
- return self.return_value
+load_django_models()
+from Bcfg2.Server.Plugins.Probes import *
+if HAS_JSON:
+ json = json
-class StoringSubElement(object):
- OriginalSubElement = copy.copy(lxml.etree.SubElement)
+if HAS_YAML:
+ yaml = yaml
- def __call__(self, parent, tag, **kwargs):
- try:
- return self.OriginalSubElement(parent._element, tag,
- **kwargs)
- except AttributeError:
- return self.OriginalSubElement(parent, tag, **kwargs)
+# test data for JSON and YAML tests
+test_data = dict(a=1, b=[1, 2, 3], c="test",
+ d=dict(a=1, b=dict(a=1), c=(1, "2", 3)))
class FakeList(list):
@@ -75,19 +43,8 @@ class FakeList(list):
class TestProbesDB(DBModelTestCase):
if HAS_DJANGO:
- models = [ProbesGroupsModel, ProbesDataModel]
-
-
-class TestClientProbeDataSet(Bcfg2TestCase):
- def test__init(self):
- ds = ClientProbeDataSet()
- self.assertLessEqual(ds.timestamp, time.time())
- self.assertIsInstance(ds, dict)
- self.assertNotIn("timestamp", ds)
-
- ds = ClientProbeDataSet(timestamp=123)
- self.assertEqual(ds.timestamp, 123)
- self.assertNotIn("timestamp", ds)
+ models = [ProbesGroupsModel,
+ ProbesDataModel]
class TestProbeData(Bcfg2TestCase):
@@ -109,19 +66,22 @@ class TestProbeData(Bcfg2TestCase):
def test_xdata(self):
xdata = lxml.etree.Element("test")
lxml.etree.SubElement(xdata, "test2")
- data = ProbeData(lxml.etree.tostring(xdata,
- xml_declaration=False).decode('UTF-8'))
+ data = ProbeData(
+ lxml.etree.tostring(xdata,
+ xml_declaration=False).decode('UTF-8'))
self.assertIsNotNone(data.xdata)
self.assertIsNotNone(data.xdata.find("test2"))
- @skipUnless(HAS_JSON, "JSON libraries not found, skipping JSON tests")
+ @skipUnless(HAS_JSON,
+ "JSON libraries not found, skipping JSON tests")
def test_json(self):
jdata = json.dumps(test_data)
data = ProbeData(jdata)
self.assertIsNotNone(data.json)
self.assertItemsEqual(test_data, data.json)
- @skipUnless(HAS_YAML, "YAML libraries not found, skipping YAML tests")
+ @skipUnless(HAS_YAML,
+ "YAML libraries not found, skipping YAML tests")
def test_yaml(self):
jdata = yaml.dump(test_data)
data = ProbeData(jdata)
@@ -135,22 +95,20 @@ class TestProbeSet(TestEntrySet):
ignore = ["foo~", ".#foo", ".foo.swp", ".foo.swx", "probed.xml"]
bogus_names = ["test.py"]
- def get_obj(self, path=datastore, fam=None, encoding=None,
+ def get_obj(self, path=datastore, encoding=None,
plugin_name="Probes", basename=None):
# get_obj() accepts the basename argument, accepted by the
# parent get_obj() method, and just throws it away, since
# ProbeSet uses a regex for the "basename"
- if fam is None:
- fam = Mock()
- rv = self.test_obj(path, fam, encoding, plugin_name)
+ rv = self.test_obj(path, plugin_name)
rv.entry_type = MagicMock()
return rv
- def test__init(self):
- fam = Mock()
- ps = self.get_obj(fam=fam)
+ @patch("Bcfg2.Server.FileMonitor.get_fam")
+ def test__init(self, mock_get_fam):
+ ps = self.get_obj()
self.assertEqual(ps.plugin_name, "Probes")
- fam.AddMonitor.assert_called_with(datastore, ps)
+ mock_get_fam.return_value.AddMonitor.assert_called_with(datastore, ps)
TestEntrySet.test__init(self)
def test_HandleEvent(self):
@@ -243,418 +201,192 @@ group-specific"""
assert False, "Strange probe found in get_probe_data() return"
-class TestProbes(TestProbing, TestConnector, TestDatabaseBacked):
+class TestProbes(TestPlugin):
test_obj = Probes
- def get_obj(self, core=None):
- return TestDatabaseBacked.get_obj(self, core=core)
-
- def get_test_probedata(self):
- test_xdata = lxml.etree.Element("test")
- lxml.etree.SubElement(test_xdata, "test", foo="foo")
- rv = dict()
- rv["foo.example.com"] = ClientProbeDataSet(timestamp=time.time())
- rv["foo.example.com"]["xml"] = \
- ProbeData(lxml.etree.tostring(test_xdata,
- xml_declaration=False).decode('UTF-8'))
- rv["foo.example.com"]["text"] = ProbeData("freeform text")
- rv["foo.example.com"]["multiline"] = ProbeData("""multiple
+ test_xdata = lxml.etree.Element("test")
+ lxml.etree.SubElement(test_xdata, "test", foo="foo")
+ test_xdoc = lxml.etree.tostring(test_xdata,
+ xml_declaration=False).decode('UTF-8')
+
+ data = dict()
+ data['xml'] = "group:group\n" + test_xdoc
+ data['text'] = "freeform text"
+ data['multiline'] = """multiple
lines
of
freeform
text
-""")
- rv["bar.example.com"] = ClientProbeDataSet(timestamp=time.time())
- rv["bar.example.com"]["empty"] = ProbeData("")
- if HAS_JSON:
- rv["bar.example.com"]["json"] = ProbeData(json.dumps(test_data))
- if HAS_YAML:
- rv["bar.example.com"]["yaml"] = ProbeData(yaml.dump(test_data))
- return rv
-
- def get_test_cgroups(self):
- return {"foo.example.com": ["group", "group with spaces",
- "group-with-dashes"],
- "bar.example.com": []}
-
- def get_probes_object(self, use_db=False, load_data=None):
- core = MagicMock()
- core.setup.cfp.getboolean = Mock()
- core.setup.cfp.getboolean.return_value = use_db
- if load_data is None:
- load_data = MagicMock()
- # we have to patch load_data() in a funny way because
- # different versions of Mock have different scopes for
- # patching. in some versions, a patch applied to
- # get_probes_object() would only apply to that function, while
- # in others it would also apply to the calling function (e.g.,
- # test__init(), which relies on being able to check the calls
- # of load_data(), and thus on load_data() being consistently
- # mocked)
- @patch("%s.%s.load_data" % (self.test_obj.__module__,
- self.test_obj.__name__), new=load_data)
- def inner():
- return self.get_obj(core)
-
- rv = inner()
- rv.allowed_cgroups = [re.compile("^.*$")]
- return rv
+group:group-with-dashes
+group: group:with:colons
+"""
+ data['empty'] = ''
+ data['almost_empty'] = 'group: other_group'
+ if HAS_JSON:
+ data['json'] = json.dumps(test_data)
+ if HAS_YAML:
+ data['yaml'] = yaml.dump(test_data)
+
+ def setUp(self):
+ Bcfg2TestCase.setUp(self)
+ set_setup_default("probes_db")
+ set_setup_default("probes_allowed_groups", [re.compile(".*")])
+ self.datastore = None
+ Bcfg2.Server.Cache.expire("Probes")
+
+ def tearDown(self):
+ Bcfg2.Server.Cache.expire("Probes")
+ if self.datastore is not None:
+ shutil.rmtree(self.datastore)
+ self.datastore = None
+ Bcfg2.Options.setup.repository = datastore
+
+ def get_obj(self):
+ if not Bcfg2.Options.setup.probes_db:
+ # actually use a real datastore so we can read and write
+ # probed.xml
+ if self.datastore is None:
+ self.datastore = tempfile.mkdtemp()
+ Bcfg2.Options.setup.repository = self.datastore
+ datadir = os.path.join(self.datastore, self.test_obj.name)
+ if not os.path.exists(datadir):
+ os.makedirs(datadir)
+ return TestPlugin.get_obj(self)
def test__init(self):
- mock_load_data = Mock()
- probes = self.get_probes_object(load_data=mock_load_data)
- probes.core.fam.AddMonitor.assert_called_with(os.path.join(datastore,
- probes.name),
- probes.probes)
- mock_load_data.assert_any_call()
- self.assertEqual(probes.probedata, ClientProbeDataSet())
- self.assertEqual(probes.cgroups, dict())
-
- @patch("Bcfg2.Server.Plugins.Probes.Probes.load_data", Mock())
- def test__use_db(self):
- probes = self.get_probes_object()
- self.assertFalse(probes._use_db)
- probes.core.setup.cfp.getboolean.assert_called_with("probes",
- "use_database",
- default=False)
-
- @skipUnless(HAS_DJANGO, "Django not found, skipping")
- @patch("Bcfg2.Server.Plugins.Probes.Probes._write_data_db", Mock())
- @patch("Bcfg2.Server.Plugins.Probes.Probes._write_data_xml", Mock())
- def test_write_data_xml(self):
- probes = self.get_probes_object(use_db=False)
- probes.write_data("test")
- probes._write_data_xml.assert_called_with("test")
- self.assertFalse(probes._write_data_db.called)
-
- @skipUnless(HAS_DJANGO, "Django not found, skipping")
- @patch("Bcfg2.Server.Plugins.Probes.Probes._write_data_db", Mock())
- @patch("Bcfg2.Server.Plugins.Probes.Probes._write_data_xml", Mock())
- def test_write_data_db(self):
- probes = self.get_probes_object(use_db=True)
- probes.write_data("test")
- probes._write_data_db.assert_called_with("test")
- self.assertFalse(probes._write_data_xml.called)
-
- def test__write_data_xml(self):
- probes = self.get_probes_object(use_db=False)
- probes.probedata = self.get_test_probedata()
- probes.cgroups = self.get_test_cgroups()
-
- @patch("lxml.etree.Element")
- @patch("lxml.etree.SubElement", StoringSubElement())
- def inner(mock_Element):
- mock_Element.side_effect = StoringElement()
- probes._write_data_xml(None)
-
- top = mock_Element.side_effect.return_value
- write = top.getroottree.return_value.write
- self.assertEqual(write.call_args[0][0],
- os.path.join(datastore, probes.name,
- "probed.xml"))
-
- data = top._element
- foodata = data.find("Client[@name='foo.example.com']")
- self.assertIsNotNone(foodata)
- self.assertIsNotNone(foodata.get("timestamp"))
- self.assertEqual(len(foodata.findall("Probe")),
- len(probes.probedata['foo.example.com']))
- self.assertEqual(len(foodata.findall("Group")),
- len(probes.cgroups['foo.example.com']))
- xml = foodata.find("Probe[@name='xml']")
- self.assertIsNotNone(xml)
- self.assertIsNotNone(xml.get("value"))
- xdata = lxml.etree.XML(xml.get("value"))
- self.assertIsNotNone(xdata)
- self.assertIsNotNone(xdata.find("test"))
- self.assertEqual(xdata.find("test").get("foo"), "foo")
- text = foodata.find("Probe[@name='text']")
- self.assertIsNotNone(text)
- self.assertIsNotNone(text.get("value"))
- multiline = foodata.find("Probe[@name='multiline']")
- self.assertIsNotNone(multiline)
- self.assertIsNotNone(multiline.get("value"))
- self.assertGreater(len(multiline.get("value").splitlines()), 1)
-
- bardata = data.find("Client[@name='bar.example.com']")
- self.assertIsNotNone(bardata)
- self.assertIsNotNone(bardata.get("timestamp"))
- self.assertEqual(len(bardata.findall("Probe")),
- len(probes.probedata['bar.example.com']))
- self.assertEqual(len(bardata.findall("Group")),
- len(probes.cgroups['bar.example.com']))
- empty = bardata.find("Probe[@name='empty']")
- self.assertIsNotNone(empty)
- self.assertIsNotNone(empty.get("value"))
- self.assertEqual(empty.get("value"), "")
- if HAS_JSON:
- jdata = bardata.find("Probe[@name='json']")
- self.assertIsNotNone(jdata)
- self.assertIsNotNone(jdata.get("value"))
- self.assertItemsEqual(test_data,
- json.loads(jdata.get("value")))
- if HAS_YAML:
- ydata = bardata.find("Probe[@name='yaml']")
- self.assertIsNotNone(ydata)
- self.assertIsNotNone(ydata.get("value"))
- self.assertItemsEqual(test_data,
- yaml.load(ydata.get("value")))
-
- inner()
-
- @skipUnless(HAS_DJANGO, "Django not found, skipping")
- def test__write_data_db(self):
- syncdb(TestProbesDB)
- probes = self.get_probes_object(use_db=True)
- probes.probedata = self.get_test_probedata()
- probes.cgroups = self.get_test_cgroups()
-
- for cname in ["foo.example.com", "bar.example.com"]:
- client = Mock()
- client.hostname = cname
- probes._write_data_db(client)
-
- pdata = ProbesDataModel.objects.filter(hostname=cname).all()
- self.assertEqual(len(pdata), len(probes.probedata[cname]))
-
- for probe in pdata:
- self.assertEqual(probe.hostname, client.hostname)
- self.assertIsNotNone(probe.data)
- if probe.probe == "xml":
- xdata = lxml.etree.XML(probe.data)
- self.assertIsNotNone(xdata)
- self.assertIsNotNone(xdata.find("test"))
- self.assertEqual(xdata.find("test").get("foo"), "foo")
- elif probe.probe == "text":
- pass
- elif probe.probe == "multiline":
- self.assertGreater(len(probe.data.splitlines()), 1)
- elif probe.probe == "empty":
- self.assertEqual(probe.data, "")
- elif probe.probe == "yaml":
- self.assertItemsEqual(test_data, yaml.load(probe.data))
- elif probe.probe == "json":
- self.assertItemsEqual(test_data, json.loads(probe.data))
- else:
- assert False, "Strange probe found in _write_data_db data"
-
- pgroups = ProbesGroupsModel.objects.filter(hostname=cname).all()
- self.assertEqual(len(pgroups), len(probes.cgroups[cname]))
-
- # test that old probe data is removed properly
- cname = 'foo.example.com'
- del probes.probedata[cname]['text']
- probes.cgroups[cname].pop()
- client = Mock()
- client.hostname = cname
- probes._write_data_db(client)
-
- pdata = ProbesDataModel.objects.filter(hostname=cname).all()
- self.assertEqual(len(pdata), len(probes.probedata[cname]))
- pgroups = ProbesGroupsModel.objects.filter(hostname=cname).all()
- self.assertEqual(len(pgroups), len(probes.cgroups[cname]))
-
- @skipUnless(HAS_DJANGO, "Django not found, skipping")
- @patch("Bcfg2.Server.Plugins.Probes.Probes._load_data_db", Mock())
- @patch("Bcfg2.Server.Plugins.Probes.Probes._load_data_xml", Mock())
- def test_load_data_xml(self):
- probes = self.get_probes_object(use_db=False)
- probes.load_data()
- probes._load_data_xml.assert_any_call()
- self.assertFalse(probes._load_data_db.called)
-
- @skipUnless(HAS_DJANGO, "Django not found, skipping")
- @patch("Bcfg2.Server.Plugins.Probes.Probes._load_data_db", Mock())
- @patch("Bcfg2.Server.Plugins.Probes.Probes._load_data_xml", Mock())
- def test_load_data_db(self):
- probes = self.get_probes_object(use_db=True)
- probes.load_data()
- probes._load_data_db.assert_any_call(client=None)
- self.assertFalse(probes._load_data_xml.called)
-
- @patch("lxml.etree.parse")
- def test__load_data_xml(self, mock_parse):
- probes = self.get_probes_object(use_db=False)
- probes.probedata = self.get_test_probedata()
- probes.cgroups = self.get_test_cgroups()
-
- # to get the value for lxml.etree.parse to parse, we call
- # _write_data_xml, mock the lxml.etree._ElementTree.write()
- # call, and grab the data that gets "written" to probed.xml
- @patch("lxml.etree.Element")
- @patch("lxml.etree.SubElement", StoringSubElement())
- def inner(mock_Element):
- mock_Element.side_effect = StoringElement()
- probes._write_data_xml(None)
- top = mock_Element.side_effect.return_value
- return top._element
-
- xdata = inner()
- mock_parse.return_value = xdata.getroottree()
- probes.probedata = dict()
- probes.cgroups = dict()
-
- probes._load_data_xml()
- mock_parse.assert_called_with(os.path.join(datastore, probes.name,
- 'probed.xml'),
- parser=Bcfg2.Server.XMLParser)
- self.assertItemsEqual(probes.probedata, self.get_test_probedata())
- self.assertItemsEqual(probes.cgroups, self.get_test_cgroups())
-
- @skipUnless(HAS_DJANGO, "Django not found, skipping")
- def test__load_data_db(self):
- syncdb(TestProbesDB)
- probes = self.get_probes_object(use_db=True)
- probes.probedata = self.get_test_probedata()
- probes.cgroups = self.get_test_cgroups()
- for cname in probes.probedata.keys():
- client = Mock()
- client.hostname = cname
- probes._write_data_db(client)
-
- probes.probedata = dict()
- probes.cgroups = dict()
- probes._load_data_db()
- self.assertItemsEqual(probes.probedata, self.get_test_probedata())
- # the db backend does not store groups at all if a client has
- # no groups set, so we can't just use assertItemsEqual here,
- # because loading saved data may _not_ result in the original
- # data if some clients had no groups set.
- test_cgroups = self.get_test_cgroups()
- for cname, groups in test_cgroups.items():
- if cname in probes.cgroups:
- self.assertEqual(groups, probes.cgroups[cname])
- else:
- self.assertEqual(groups, [])
+ if Bcfg2.Options.setup.probes_db:
+ TestPlugin.test__init(self)
- @patch("Bcfg2.Server.Plugins.Probes.ProbeSet.get_probe_data")
- def test_GetProbes(self, mock_get_probe_data):
- probes = self.get_probes_object()
+ def test_GetProbes(self):
+ p = self.get_obj()
+ p.probes = Mock()
metadata = Mock()
- probes.GetProbes(metadata)
- mock_get_probe_data.assert_called_with(metadata)
-
- @patch("Bcfg2.Server.Plugins.Probes.Probes.write_data")
- @patch("Bcfg2.Server.Plugins.Probes.Probes.ReceiveDataItem")
- def test_ReceiveData(self, mock_ReceiveDataItem, mock_write_data):
- # we use a simple (read: bogus) datalist here to make this
- # easy to test
- datalist = ["a", "b", "c"]
-
- probes = self.get_probes_object()
- probes.core.metadata_cache_mode = 'off'
+ p.GetProbes(metadata)
+ p.probes.get_probe_data.assert_called_with(metadata)
+
+ def additionalDataEqual(self, actual, expected):
+ self.assertItemsEqual(
+ dict([(k, str(d)) for k, d in actual.items()]),
+ expected)
+
+ def test_probes_xml(self):
+ """ Set and retrieve probe data with database disabled """
+ Bcfg2.Options.setup.probes_db = False
+ self._perform_tests()
+
+ @skipUnless(HAS_DJANGO, "Django not found")
+ def test_probes_db(self):
+ """ Set and retrieve probe data with database enabled """
+ Bcfg2.Options.setup.probes_db = True
+ syncdb(TestProbesDB)
+ self._perform_tests()
+
+ def test_allowed_cgroups(self):
+ """ Test option to only allow probes to set certain groups """
+ probes = self.get_obj()
+
+ test_text = """a couple lines
+of freeform text
+"""
+ test_groups = ["group", "group2", "group-with-dashes"]
+ test_probe_data = lxml.etree.Element("Probe", name="test")
+ test_probe_data.text = test_text
+ for group in test_groups:
+ test_probe_data.text += "group:%s\n" % group
+
client = Mock()
- client.hostname = "foo.example.com"
- probes.ReceiveData(client, datalist)
-
- cgroups = []
- cprobedata = ClientProbeDataSet()
- self.assertItemsEqual(mock_ReceiveDataItem.call_args_list,
- [call(client, "a", cgroups, cprobedata),
- call(client, "b", cgroups, cprobedata),
- call(client, "c", cgroups, cprobedata)])
- mock_write_data.assert_called_with(client)
- self.assertFalse(probes.core.metadata_cache.expire.called)
-
- # change the datalist, ensure that the cache is cleared
- probes.cgroups[client.hostname] = datalist
- probes.core.metadata_cache_mode = 'aggressive'
- probes.ReceiveData(client, ['a', 'b', 'd'])
-
- mock_write_data.assert_called_with(client)
- probes.core.metadata_cache.expire.assert_called_with(client.hostname)
-
- def test_ReceiveDataItem(self):
- probes = self.get_probes_object()
- for cname, cdata in self.get_test_probedata().items():
- client = Mock()
- client.hostname = cname
- cgroups = []
- cprobedata = ClientProbeDataSet()
- for pname, pdata in cdata.items():
- dataitem = lxml.etree.Element("Probe", name=pname)
- if pname == "text":
- # add some groups to the plaintext test to test
- # group parsing
- data = [pdata]
- for group in self.get_test_cgroups()[cname]:
- data.append("group:%s" % group)
- dataitem.text = "\n".join(data)
- else:
- dataitem.text = str(pdata)
-
- probes.ReceiveDataItem(client, dataitem, cgroups, cprobedata)
-
- probes.cgroups[client.hostname] = cgroups
- probes.probedata[client.hostname] = cprobedata
- self.assertIn(client.hostname, probes.probedata)
- self.assertIn(pname, probes.probedata[cname])
- self.assertEqual(pdata, probes.probedata[cname][pname])
- self.assertIn(client.hostname, probes.cgroups)
- self.assertEqual(probes.cgroups[cname],
- self.get_test_cgroups()[cname])
-
- # test again, with an explicit list of allowed groups
- probes.allowed_cgroups = [re.compile(r'^.*s$')]
- for cname, cdata in self.get_test_probedata().items():
- client = Mock()
- client.hostname = cname
- cgroups = []
- cprobedata = ClientProbeDataSet()
- for pname, pdata in cdata.items():
- dataitem = lxml.etree.Element("Probe", name=pname)
- if pname == "text":
- # add some groups to the plaintext test to test
- # group parsing
- data = [pdata]
- for group in self.get_test_cgroups()[cname]:
- data.append("group:%s" % group)
- dataitem.text = "\n".join(data)
- else:
- dataitem.text = str(pdata)
-
- probes.ReceiveDataItem(client, dataitem, cgroups, cprobedata)
-
- probes.cgroups[client.hostname] = cgroups
- probes.probedata[client.hostname] = cprobedata
- self.assertIn(client.hostname, probes.probedata)
- self.assertIn(pname, probes.probedata[cname])
- self.assertEqual(pdata, probes.probedata[cname][pname])
- self.assertIn(client.hostname, probes.cgroups)
- self.assertEqual(probes.cgroups[cname],
- [g for g in self.get_test_cgroups()[cname]
- if g.endswith("s")])
-
- def test_get_additional_groups(self):
- TestConnector.test_get_additional_groups(self)
-
- probes = self.get_probes_object()
- test_cgroups = self.get_test_cgroups()
- probes.cgroups = self.get_test_cgroups()
- for cname in test_cgroups.keys():
- metadata = Mock()
- metadata.hostname = cname
- self.assertEqual(test_cgroups[cname],
- probes.get_additional_groups(metadata))
- # test a non-existent client
- metadata = Mock()
- metadata.hostname = "nonexistent"
- self.assertEqual(probes.get_additional_groups(metadata),
- list())
-
- def test_get_additional_data(self):
- TestConnector.test_get_additional_data(self)
-
- probes = self.get_probes_object()
- test_probedata = self.get_test_probedata()
- probes.probedata = self.get_test_probedata()
- for cname in test_probedata.keys():
- metadata = Mock()
- metadata.hostname = cname
- self.assertEqual(test_probedata[cname],
- probes.get_additional_data(metadata))
- # test a non-existent client
- metadata = Mock()
- metadata.hostname = "nonexistent"
- self.assertEqual(probes.get_additional_data(metadata),
- ClientProbeDataSet())
+ groups, data = probes.ReceiveDataItem(client, test_probe_data)
+ self.assertItemsEqual(groups, test_groups)
+ self.assertEqual(data, test_text)
+
+ old_allowed_groups = Bcfg2.Options.setup.probes_allowed_groups
+ Bcfg2.Options.setup.probes_allowed_groups = [re.compile(r'^group.?$')]
+ groups, data = probes.ReceiveDataItem(client, test_probe_data)
+ self.assertItemsEqual(groups, ['group', 'group2'])
+ self.assertEqual(data, test_text)
+ Bcfg2.Options.setup.probes_allowed_groups = old_allowed_groups
+
+ def _perform_tests(self):
+ p = self.get_obj()
+
+ # first, sanity checks
+ foo_md = Mock(hostname="foo.example.com")
+ bar_md = Mock(hostname="bar.example.com")
+ self.assertItemsEqual(p.get_additional_groups(foo_md), [])
+ self.assertItemsEqual(p.get_additional_data(foo_md), dict())
+ self.assertItemsEqual(p.get_additional_groups(bar_md), [])
+ self.assertItemsEqual(p.get_additional_data(bar_md), dict())
+
+ # next, set some initial probe data
+ foo_datalist = []
+ for key in ['xml', 'text', 'multiline']:
+ pdata = lxml.etree.Element("Probe", name=key)
+ pdata.text = self.data[key]
+ foo_datalist.append(pdata)
+ foo_addl_data = dict(xml=self.test_xdoc,
+ text="freeform text",
+ multiline="""multiple
+lines
+of
+freeform
+text""")
+ bar_datalist = []
+ for key in ['empty', 'almost_empty', 'json', 'yaml']:
+ if key in self.data:
+ pdata = lxml.etree.Element("Probe", name=key)
+ pdata.text = self.data[key]
+ bar_datalist.append(pdata)
+ bar_addl_data = dict(empty="", almost_empty="")
+ if HAS_JSON:
+ bar_addl_data['json'] = self.data['json']
+ if HAS_YAML:
+ bar_addl_data['yaml'] = self.data['yaml']
+
+ p.ReceiveData(foo_md, foo_datalist)
+ self.assertItemsEqual(p.get_additional_groups(foo_md),
+ ["group", "group-with-dashes",
+ "group:with:colons"])
+ self.additionalDataEqual(p.get_additional_data(foo_md), foo_addl_data)
+
+ p.ReceiveData(bar_md, bar_datalist)
+ self.assertItemsEqual(p.get_additional_groups(foo_md),
+ ["group", "group-with-dashes",
+ "group:with:colons"])
+ self.additionalDataEqual(p.get_additional_data(foo_md), foo_addl_data)
+ self.assertItemsEqual(p.get_additional_groups(bar_md), ['other_group'])
+ self.additionalDataEqual(p.get_additional_data(bar_md), bar_addl_data)
+
+ # instantiate a new Probes object and clear Probes caches to
+ # imitate a server restart
+ p = self.get_obj()
+ Bcfg2.Server.Cache.expire("Probes")
+
+ self.assertItemsEqual(p.get_additional_groups(foo_md),
+ ["group", "group-with-dashes",
+ "group:with:colons"])
+ self.additionalDataEqual(p.get_additional_data(foo_md), foo_addl_data)
+ self.assertItemsEqual(p.get_additional_groups(bar_md), ['other_group'])
+ self.additionalDataEqual(p.get_additional_data(bar_md), bar_addl_data)
+
+ # set new data (and groups) for foo
+ foo_datalist = []
+ pdata = lxml.etree.Element("Probe", name='xml')
+ pdata.text = self.data['xml']
+ foo_datalist.append(pdata)
+ foo_addl_data = dict(xml=self.test_xdoc)
+
+ p.ReceiveData(foo_md, foo_datalist)
+ self.assertItemsEqual(p.get_additional_groups(foo_md), ["group"])
+ self.additionalDataEqual(p.get_additional_data(foo_md), foo_addl_data)
+ self.assertItemsEqual(p.get_additional_groups(bar_md), ['other_group'])
+ self.additionalDataEqual(p.get_additional_data(bar_md), bar_addl_data)
+
+ # instantiate a new Probes object and clear Probes caches to
+ # imitate a server restart
+ p = self.get_obj()
+ Bcfg2.Server.Cache.expire("Probes")
+
+ self.assertItemsEqual(p.get_additional_groups(foo_md), ["group"])
+ self.additionalDataEqual(p.get_additional_data(foo_md), foo_addl_data)
+ self.assertItemsEqual(p.get_additional_groups(bar_md), ['other_group'])
+ self.additionalDataEqual(p.get_additional_data(bar_md), bar_addl_data)
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProperties.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProperties.py
index 896f5861e..159dc6e66 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProperties.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProperties.py
@@ -19,12 +19,6 @@ from TestPlugin import TestStructFile, TestFileBacked, TestConnector, \
TestPlugin, TestDirectoryBacked
try:
- from Bcfg2.Encryption import EVPError
- HAS_CRYPTO = True
-except:
- HAS_CRYPTO = False
-
-try:
import json
JSON = "json"
except ImportError:
@@ -36,12 +30,12 @@ class TestPropertyFile(Bcfg2TestCase):
path = os.path.join(datastore, "test")
def get_obj(self, path=None):
+ set_setup_default("writes_enabled", False)
if path is None:
path = self.path
return self.test_obj(path)
def test_write(self):
- Bcfg2.Server.Plugins.Properties.SETUP = MagicMock()
pf = self.get_obj()
pf.validate_data = Mock()
pf._write = Mock()
@@ -52,20 +46,16 @@ class TestPropertyFile(Bcfg2TestCase):
def reset():
pf.validate_data.reset_mock()
pf._write.reset_mock()
- Bcfg2.Server.Plugins.Properties.SETUP.reset_mock()
# test writes disabled
- Bcfg2.Server.Plugins.Properties.SETUP.cfp.getboolean.return_value = False
+ Bcfg2.Options.setup.writes_enabled = False
self.assertRaises(PluginExecutionError, pf.write)
self.assertFalse(pf.validate_data.called)
self.assertFalse(pf._write.called)
- Bcfg2.Server.Plugins.Properties.SETUP.cfp.getboolean.assert_called_with("properties",
- "writes_enabled",
- default=True)
# test successful write
reset()
- Bcfg2.Server.Plugins.Properties.SETUP.cfp.getboolean.return_value = True
+ Bcfg2.Options.setup.writes_enabled = True
self.assertEqual(pf.write(), pf._write.return_value)
pf.validate_data.assert_called_with()
pf._write.assert_called_with()
@@ -99,96 +89,95 @@ class TestPropertyFile(Bcfg2TestCase):
mock_copy.assert_called_with(pf)
-if can_skip or HAS_JSON:
- class TestJSONPropertyFile(TestFileBacked, TestPropertyFile):
- test_obj = JSONPropertyFile
-
- def get_obj(self, *args, **kwargs):
- return TestFileBacked.get_obj(self, *args, **kwargs)
-
- @skipUnless(HAS_JSON, "JSON libraries not found, skipping")
- def setUp(self):
- pass
-
- @patch("%s.loads" % JSON)
- def test_Index(self, mock_loads):
- pf = self.get_obj()
- pf.Index()
- mock_loads.assert_called_with(pf.data)
- self.assertEqual(pf.json, mock_loads.return_value)
-
- mock_loads.reset_mock()
- mock_loads.side_effect = ValueError
- self.assertRaises(PluginExecutionError, pf.Index)
- mock_loads.assert_called_with(pf.data)
-
- @patch("%s.dump" % JSON)
- @patch("%s.open" % builtins)
- def test__write(self, mock_open, mock_dump):
- pf = self.get_obj()
- self.assertTrue(pf._write())
- mock_open.assert_called_with(pf.name, 'wb')
- mock_dump.assert_called_with(pf.json, mock_open.return_value)
-
- @patch("%s.dumps" % JSON)
- def test_validate_data(self, mock_dumps):
- pf = self.get_obj()
- pf.validate_data()
- mock_dumps.assert_called_with(pf.json)
-
- mock_dumps.reset_mock()
- mock_dumps.side_effect = ValueError
- self.assertRaises(PluginExecutionError, pf.validate_data)
- mock_dumps.assert_called_with(pf.json)
-
-
-if can_skip or HAS_YAML:
- class TestYAMLPropertyFile(TestFileBacked, TestPropertyFile):
- test_obj = YAMLPropertyFile
-
- def get_obj(self, *args, **kwargs):
- return TestFileBacked.get_obj(self, *args, **kwargs)
-
- @skipUnless(HAS_YAML, "YAML libraries not found, skipping")
- def setUp(self):
- pass
-
- @patch("yaml.load")
- def test_Index(self, mock_load):
- pf = self.get_obj()
- pf.Index()
- mock_load.assert_called_with(pf.data)
- self.assertEqual(pf.yaml, mock_load.return_value)
-
- mock_load.reset_mock()
- mock_load.side_effect = yaml.YAMLError
- self.assertRaises(PluginExecutionError, pf.Index)
- mock_load.assert_called_with(pf.data)
-
- @patch("yaml.dump")
- @patch("%s.open" % builtins)
- def test__write(self, mock_open, mock_dump):
- pf = self.get_obj()
- self.assertTrue(pf._write())
- mock_open.assert_called_with(pf.name, 'wb')
- mock_dump.assert_called_with(pf.yaml, mock_open.return_value)
-
- @patch("yaml.dump")
- def test_validate_data(self, mock_dump):
- pf = self.get_obj()
- pf.validate_data()
- mock_dump.assert_called_with(pf.yaml)
-
- mock_dump.reset_mock()
- mock_dump.side_effect = yaml.YAMLError
- self.assertRaises(PluginExecutionError, pf.validate_data)
- mock_dump.assert_called_with(pf.yaml)
+class TestJSONPropertyFile(TestFileBacked, TestPropertyFile):
+ test_obj = JSONPropertyFile
+
+ @skipUnless(HAS_JSON, "JSON libraries not found, skipping")
+ def setUp(self):
+ TestFileBacked.setUp(self)
+ TestPropertyFile.setUp(self)
+
+ @patch("%s.loads" % JSON)
+ def test_Index(self, mock_loads):
+ pf = self.get_obj()
+ pf.Index()
+ mock_loads.assert_called_with(pf.data)
+ self.assertEqual(pf.json, mock_loads.return_value)
+
+ mock_loads.reset_mock()
+ mock_loads.side_effect = ValueError
+ self.assertRaises(PluginExecutionError, pf.Index)
+ mock_loads.assert_called_with(pf.data)
+
+ @patch("%s.dump" % JSON)
+ @patch("%s.open" % builtins)
+ def test__write(self, mock_open, mock_dump):
+ pf = self.get_obj()
+ self.assertTrue(pf._write())
+ mock_open.assert_called_with(pf.name, 'wb')
+ mock_dump.assert_called_with(pf.json, mock_open.return_value)
+
+ @patch("%s.dumps" % JSON)
+ def test_validate_data(self, mock_dumps):
+ pf = self.get_obj()
+ pf.validate_data()
+ mock_dumps.assert_called_with(pf.json)
+
+ mock_dumps.reset_mock()
+ mock_dumps.side_effect = ValueError
+ self.assertRaises(PluginExecutionError, pf.validate_data)
+ mock_dumps.assert_called_with(pf.json)
+
+
+class TestYAMLPropertyFile(TestFileBacked, TestPropertyFile):
+ test_obj = YAMLPropertyFile
+
+ @skipUnless(HAS_YAML, "YAML libraries not found, skipping")
+ def setUp(self):
+ TestFileBacked.setUp(self)
+ TestPropertyFile.setUp(self)
+
+ @patch("yaml.load")
+ def test_Index(self, mock_load):
+ pf = self.get_obj()
+ pf.Index()
+ mock_load.assert_called_with(pf.data)
+ self.assertEqual(pf.yaml, mock_load.return_value)
+
+ mock_load.reset_mock()
+ mock_load.side_effect = yaml.YAMLError
+ self.assertRaises(PluginExecutionError, pf.Index)
+ mock_load.assert_called_with(pf.data)
+
+ @patch("yaml.dump")
+ @patch("%s.open" % builtins)
+ def test__write(self, mock_open, mock_dump):
+ pf = self.get_obj()
+ self.assertTrue(pf._write())
+ mock_open.assert_called_with(pf.name, 'wb')
+ mock_dump.assert_called_with(pf.yaml, mock_open.return_value)
+
+ @patch("yaml.dump")
+ def test_validate_data(self, mock_dump):
+ pf = self.get_obj()
+ pf.validate_data()
+ mock_dump.assert_called_with(pf.yaml)
+
+ mock_dump.reset_mock()
+ mock_dump.side_effect = yaml.YAMLError
+ self.assertRaises(PluginExecutionError, pf.validate_data)
+ mock_dump.assert_called_with(pf.yaml)
class TestXMLPropertyFile(TestPropertyFile, TestStructFile):
test_obj = XMLPropertyFile
path = TestStructFile.path
+ def setUp(self):
+ TestPropertyFile.setUp(self)
+ TestStructFile.setUp(self)
+ set_setup_default("automatch", False)
+
def get_obj(self, *args, **kwargs):
return TestStructFile.get_obj(self, *args, **kwargs)
@@ -243,178 +232,47 @@ class TestXMLPropertyFile(TestPropertyFile, TestStructFile):
mock_exists.assert_called_with(schemafile)
mock_XMLSchema.assert_called_with(file=schemafile)
- def test_Index(self):
- TestStructFile.test_Index(self)
-
- pf = self.get_obj()
- pf.xdata = lxml.etree.Element("Properties")
- lxml.etree.SubElement(pf.xdata, "Crypted", encrypted="foo")
- pf.data = lxml.etree.tostring(pf.xdata)
-
- @skipUnless(HAS_CRYPTO, "No crypto libraries found, skipping")
- def test_Index_crypto(self):
- pf = self.get_obj()
- pf._decrypt = Mock()
- pf._decrypt.return_value = 'plaintext'
- pf.data = '''
-<Properties decrypt="strict">
- <Crypted encrypted="foo">
- crypted
- <Plain foo="bar">plain</Plain>
- </Crypted>
- <Crypted encrypted="bar">crypted</Crypted>
- <Plain bar="baz">plain</Plain>
- <Plain>
- <Crypted encrypted="foo">crypted</Crypted>
- </Plain>
-</Properties>'''
-
- # test successful decryption
- pf.Index()
- self.assertItemsEqual(pf._decrypt.call_args_list,
- [call(el) for el in pf.xdata.xpath("//Crypted")])
- for el in pf.xdata.xpath("//Crypted"):
- self.assertEqual(el.text, pf._decrypt.return_value)
-
- # test failed decryption, strict
- pf._decrypt.reset_mock()
- pf._decrypt.side_effect = EVPError
- self.assertRaises(PluginExecutionError, pf.Index)
-
- # test failed decryption, lax
- pf.data = pf.data.replace("strict", "lax")
- pf._decrypt.reset_mock()
- pf.Index()
- self.assertItemsEqual(pf._decrypt.call_args_list,
- [call(el) for el in pf.xdata.xpath("//Crypted")])
-
- @skipUnless(HAS_CRYPTO, "No crypto libraries found, skipping")
- @patchIf(HAS_CRYPTO, "Bcfg2.Encryption.ssl_decrypt")
- @patchIf(HAS_CRYPTO, "Bcfg2.Encryption.get_algorithm")
- @patchIf(HAS_CRYPTO, "Bcfg2.Encryption.get_passphrases")
- @patchIf(HAS_CRYPTO, "Bcfg2.Encryption.bruteforce_decrypt")
- def test_decrypt(self, mock_bruteforce, mock_get_passphrases,
- mock_get_algorithm, mock_ssl):
- pf = self.get_obj()
- Bcfg2.Server.Plugins.Properties.SETUP = MagicMock()
-
- def reset():
- mock_bruteforce.reset_mock()
- mock_get_algorithm.reset_mock()
- mock_get_passphrases.reset_mock()
- mock_ssl.reset_mock()
-
- # test element without text contents
- self.assertIsNone(pf._decrypt(lxml.etree.Element("Test")))
- self.assertFalse(mock_bruteforce.called)
- self.assertFalse(mock_get_passphrases.called)
- self.assertFalse(mock_ssl.called)
-
- # test element with a passphrase in the config file
- reset()
- el = lxml.etree.Element("Test", encrypted="foo")
- el.text = "crypted"
- mock_get_passphrases.return_value = dict(foo="foopass",
- bar="barpass")
- mock_get_algorithm.return_value = "bf_cbc"
- mock_ssl.return_value = "decrypted with ssl"
- self.assertEqual(pf._decrypt(el), mock_ssl.return_value)
- mock_get_passphrases.assert_called_with(
- Bcfg2.Server.Plugins.Properties.SETUP)
- mock_get_algorithm.assert_called_with(
- Bcfg2.Server.Plugins.Properties.SETUP)
- mock_ssl.assert_called_with(el.text, "foopass",
- algorithm="bf_cbc")
- self.assertFalse(mock_bruteforce.called)
-
- # test failure to decrypt element with a passphrase in the config
- reset()
- mock_ssl.side_effect = EVPError
- self.assertRaises(EVPError, pf._decrypt, el)
- mock_get_passphrases.assert_called_with(
- Bcfg2.Server.Plugins.Properties.SETUP)
- mock_get_algorithm.assert_called_with(
- Bcfg2.Server.Plugins.Properties.SETUP)
- mock_ssl.assert_called_with(el.text, "foopass",
- algorithm="bf_cbc")
- self.assertFalse(mock_bruteforce.called)
-
- # test element without valid passphrase
- reset()
- el.set("encrypted", "true")
- mock_bruteforce.return_value = "decrypted with bruteforce"
- self.assertEqual(pf._decrypt(el), mock_bruteforce.return_value)
- mock_get_passphrases.assert_called_with(
- Bcfg2.Server.Plugins.Properties.SETUP)
- mock_get_algorithm.assert_called_with(
- Bcfg2.Server.Plugins.Properties.SETUP)
- mock_bruteforce.assert_called_with(el.text,
- passphrases=["foopass",
- "barpass"],
- algorithm="bf_cbc")
- self.assertFalse(mock_ssl.called)
-
- # test failure to decrypt element without valid passphrase
- reset()
- mock_bruteforce.side_effect = EVPError
- self.assertRaises(EVPError, pf._decrypt, el)
- mock_get_passphrases.assert_called_with(
- Bcfg2.Server.Plugins.Properties.SETUP)
- mock_get_algorithm.assert_called_with(
- Bcfg2.Server.Plugins.Properties.SETUP)
- mock_bruteforce.assert_called_with(el.text,
- passphrases=["foopass",
- "barpass"],
- algorithm="bf_cbc")
- self.assertFalse(mock_ssl.called)
-
@patch("copy.copy")
def test_get_additional_data(self, mock_copy):
- Bcfg2.Server.Plugins.Properties.SETUP = Mock()
pf = self.get_obj()
+ pf.setup = Mock()
pf.XMLMatch = Mock()
metadata = Mock()
def reset():
mock_copy.reset_mock()
pf.XMLMatch.reset_mock()
- Bcfg2.Server.Plugins.Properties.SETUP.reset_mock()
+ pf.setup.reset_mock()
pf.xdata = lxml.etree.Element("Properties", automatch="true")
- for automatch in [True, False]:
+ for Bcfg2.Options.setup.automatch in [True, False]:
reset()
- Bcfg2.Server.Plugins.Properties.SETUP.cfp.getboolean.return_value = automatch
self.assertEqual(pf.get_additional_data(metadata),
pf.XMLMatch.return_value)
pf.XMLMatch.assert_called_with(metadata)
- Bcfg2.Server.Plugins.Properties.SETUP.cfp.getboolean.assert_called_with("properties", "automatch", default=False)
self.assertFalse(mock_copy.called)
pf.xdata = lxml.etree.Element("Properties", automatch="false")
- for automatch in [True, False]:
+ for Bcfg2.Options.setup.automatch in [True, False]:
reset()
- Bcfg2.Server.Plugins.Properties.SETUP.cfp.getboolean.return_value = automatch
self.assertEqual(pf.get_additional_data(metadata),
mock_copy.return_value)
mock_copy.assert_called_with(pf)
self.assertFalse(pf.XMLMatch.called)
- Bcfg2.Server.Plugins.Properties.SETUP.cfp.getboolean.assert_called_with("properties", "automatch", default=False)
pf.xdata = lxml.etree.Element("Properties")
reset()
- Bcfg2.Server.Plugins.Properties.SETUP.cfp.getboolean.return_value = False
+ Bcfg2.Options.setup.automatch = False
self.assertEqual(pf.get_additional_data(metadata),
mock_copy.return_value)
mock_copy.assert_called_with(pf)
self.assertFalse(pf.XMLMatch.called)
- Bcfg2.Server.Plugins.Properties.SETUP.cfp.getboolean.assert_called_with("properties", "automatch", default=False)
reset()
- Bcfg2.Server.Plugins.Properties.SETUP.cfp.getboolean.return_value = True
+ Bcfg2.Options.setup.automatch = True
self.assertEqual(pf.get_additional_data(metadata),
pf.XMLMatch.return_value)
pf.XMLMatch.assert_called_with(metadata)
- Bcfg2.Server.Plugins.Properties.SETUP.cfp.getboolean.assert_called_with("properties", "automatch", default=False)
self.assertFalse(mock_copy.called)
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestRules.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestRules.py
index f018b45dc..45f3671e8 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestRules.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestRules.py
@@ -1,9 +1,11 @@
import os
import sys
+import copy
import lxml.etree
-import Bcfg2.Server.Plugin
+import Bcfg2.Options
from mock import Mock, MagicMock, patch
from Bcfg2.Server.Plugins.Rules import *
+from Bcfg2.Server.Plugin import PluginExecutionError
# add all parent testsuite directories to sys.path to allow (most)
# relative imports in python 2.4
@@ -15,116 +17,159 @@ while path != "/":
break
path = os.path.dirname(path)
from common import *
-from TestPlugin import TestPrioDir
+from TestPlugin.Testhelpers import TestPrioDir
class TestRules(TestPrioDir):
test_obj = Rules
- def test_HandlesEntry(self):
+ abstract = dict(
+ basic=lxml.etree.Element("Path", name="/etc/basic"),
+ unhandled=lxml.etree.Element("Path", name="/etc/unhandled"),
+ priority=lxml.etree.Element("Path", name="/etc/priority"),
+ content=lxml.etree.Element("Path", name="/etc/text-content"),
+ duplicate=lxml.etree.Element("SEBoolean", name="duplicate"),
+ group=lxml.etree.Element("SEPort", name="6789/tcp"),
+ children=lxml.etree.Element("Path", name="/etc/child-entries"),
+ regex=lxml.etree.Element("Package", name="regex"),
+ slash=lxml.etree.Element("Path", name="/etc/trailing/slash"),
+ no_slash=lxml.etree.Element("Path", name="/etc/no/trailing/slash/"))
+
+ concrete = dict(
+ basic=lxml.etree.Element("Path", name="/etc/basic", type="directory",
+ owner="root", group="root", mode="0600"),
+ priority=lxml.etree.Element("Path", name="/etc/priority",
+ type="directory", owner="root",
+ group="root", mode="0600"),
+ content=lxml.etree.Element("Path", name="/etc/text-content",
+ type="file", owner="bar", group="bar",
+ mode="0644"),
+ duplicate=lxml.etree.Element("SEBoolean", name="duplicate",
+ value="on"),
+ group=lxml.etree.Element("SEPort", name="6789/tcp",
+ selinuxtype="bcfg2_server_t"),
+ children=lxml.etree.Element("Path", name="/etc/child-entries",
+ type="directory", owner="root",
+ group="root", mode="0775"),
+ regex=lxml.etree.Element("Package", name="regex", type="yum",
+ version="any"),
+ slash=lxml.etree.Element("Path", name="/etc/trailing/slash",
+ type="directory", owner="root", group="root",
+ mode="0600"),
+ no_slash=lxml.etree.Element("Path", name="/etc/no/trailing/slash/",
+ type="directory", owner="root",
+ group="root", mode="0600"))
+
+ concrete['content'].text = "Text content"
+ lxml.etree.SubElement(concrete['children'],
+ "ACL", type="default", scope="user", user="foouser",
+ perms="rw")
+ lxml.etree.SubElement(concrete['children'],
+ "ACL", type="default", scope="group", group="users",
+ perms="rx")
+
+ in_file = copy.deepcopy(concrete)
+ in_file['regex'].set("name", ".*")
+ in_file['slash'].set("name", "/etc/trailing/slash/")
+ in_file['no_slash'].set("name", "/etc/no/trailing/slash")
+
+ rules1 = lxml.etree.Element("Rules", priority="10")
+ rules1.append(in_file['basic'])
+ lxml.etree.SubElement(rules1, "Path", name="/etc/priority",
+ type="directory", owner="foo", group="foo",
+ mode="0644")
+ foogroup = lxml.etree.SubElement(rules1, "Group", name="foogroup")
+ foogroup.append(in_file['group'])
+ rules1.append(in_file['content'])
+ rules1.append(copy.copy(in_file['duplicate']))
+
+ rules2 = lxml.etree.Element("Rules", priority="20")
+ rules2.append(in_file['priority'])
+ rules2.append(in_file['children'])
+ rules2.append(in_file['no_slash'])
+
+ rules3 = lxml.etree.Element("Rules", priority="10")
+ rules3.append(in_file['duplicate'])
+ rules3.append(in_file['regex'])
+ rules3.append(in_file['slash'])
+
+ rules = {"rules1.xml": rules1, "rules2.xml": rules2, "rules3.xml": rules3}
+
+ def setUp(self):
+ TestPrioDir.setUp(self)
+ set_setup_default("lax_decryption", True)
+ set_setup_default("rules_regex", False)
+
+ def get_child(self, name):
+ """ Turn one of the XML documents in `rules` into a child
+ object """
+ filename = os.path.join(datastore, self.test_obj.name, name)
+ rv = self.test_obj.__child__(filename)
+ rv.data = lxml.etree.tostring(self.rules[name])
+ rv.Index()
+ return rv
+
+ def get_obj(self, core=None):
+ r = TestPrioDir.get_obj(self, core=core)
+ r.entries = dict([(n, self.get_child(n)) for n in self.rules.keys()])
+ return r
+
+ def _do_test(self, name, groups=None):
+ if groups is None:
+ groups = []
r = self.get_obj()
- r.Entries = dict(Path={"/etc/foo.conf": Mock(),
- "/etc/bar.conf": Mock()})
- r._matches = Mock()
- metadata = Mock()
-
- entry = lxml.etree.Element("Path", name="/etc/foo.conf")
- self.assertEqual(r.HandlesEntry(entry, metadata),
- r._matches.return_value)
- r._matches.assert_called_with(entry, metadata,
- r.Entries['Path'].keys())
-
- r._matches.reset_mock()
- entry = lxml.etree.Element("Path", name="/etc/baz.conf")
- self.assertEqual(r.HandlesEntry(entry, metadata),
- r._matches.return_value)
- r._matches.assert_called_with(entry, metadata,
- r.Entries['Path'].keys())
-
- r._matches.reset_mock()
- entry = lxml.etree.Element("Package", name="foo")
- self.assertFalse(r.HandlesEntry(entry, metadata))
-
- def test_BindEntry(self, method="BindEntry"):
+ metadata = Mock(groups=groups)
+ entry = copy.deepcopy(self.abstract[name])
+ self.assertTrue(r.HandlesEntry(entry, metadata))
+ r.HandleEntry(entry, metadata)
+ self.assertXMLEqual(entry, self.concrete[name])
+
+ def _do_test_failure(self, name, groups=None, handles=None):
+ if groups is None:
+ groups = []
r = self.get_obj()
- r.get_attrs = Mock()
- r.get_attrs.return_value = dict(overwrite="new", add="add",
- text="text")
- entry = lxml.etree.Element("Test", overwrite="old", keep="keep")
- metadata = Mock()
-
- getattr(r, method)(entry, metadata)
- r.get_attrs.assert_called_with(entry, metadata)
- self.assertItemsEqual(entry.attrib,
- dict(overwrite="old", add="add", keep="keep",
- text="text"))
-
- def test_HandleEntry(self):
- self.test_BindEntry(method="HandleEntry")
-
- @patch("Bcfg2.Server.Plugin.PrioDir._matches")
- def test__matches(self, mock_matches):
- """ test _matches() behavior regardless of state of _regex_enabled """
- r = self.get_obj()
- metadata = Mock()
-
- entry = lxml.etree.Element("Path", name="/etc/foo.conf")
- rules = []
- mock_matches.return_value = True
- self.assertTrue(r._matches(entry, metadata, rules))
- mock_matches.assert_called_with(r, entry, metadata, rules)
-
- # test special Path cases -- adding and removing trailing slash
- mock_matches.reset_mock()
- mock_matches.return_value = False
- rules = ["/etc/foo/", "/etc/bar"]
- entry = lxml.etree.Element("Path", name="/etc/foo")
- self.assertTrue(r._matches(entry, metadata, rules))
- mock_matches.assert_called_with(r, entry, metadata, rules)
-
- mock_matches.reset_mock()
- entry = lxml.etree.Element("Path", name="/etc/bar/")
- self.assertTrue(r._matches(entry, metadata, rules))
- mock_matches.assert_called_with(r, entry, metadata, rules)
-
- @patch("Bcfg2.Server.Plugin.PrioDir._matches")
- def test__matches_regex_disabled(self, mock_matches):
- """ test failure to match with regex disabled """
- r = self.get_obj()
- self.set_regex_enabled(r, False)
- metadata = Mock()
- mock_matches.return_value = False
-
- entry = lxml.etree.Element("Path", name="/etc/foo.conf")
- rules = []
- self.assertFalse(r._matches(entry, metadata, rules))
- mock_matches.assert_called_with(r, entry, metadata, rules)
-
- @patch("Bcfg2.Server.Plugin.PrioDir._matches")
- def test__matches_regex_enabled(self, mock_matches):
- """ test match with regex enabled """
- r = self.get_obj()
- self.set_regex_enabled(r, True)
- metadata = Mock()
- mock_matches.return_value = False
-
- entry = lxml.etree.Element("Path", name="/etc/foo.conf")
- rules = ["/etc/.*\.conf", "/etc/bar"]
- self.assertTrue(r._matches(entry, metadata, rules))
- mock_matches.assert_called_with(r, entry, metadata, rules)
- self.assertIn("/etc/.*\.conf", r._regex_cache.keys())
-
- def set_regex_enabled(self, rules_obj, state):
- """ set the state of regex_enabled for this implementation of
- Rules """
- if not isinstance(rules_obj.core.setup, MagicMock):
- rules_obj.core.setup = MagicMock()
- rules_obj.core.setup.cfp.getboolean.return_value = state
-
- def test__regex_enabled(self):
- r = self.get_obj()
- r.core.setup = MagicMock()
- self.assertEqual(r._regex_enabled,
- r.core.setup.cfp.getboolean.return_value)
- r.core.setup.cfp.getboolean.assert_called_with("rules", "regex",
- default=False)
+ metadata = Mock(groups=groups)
+ entry = self.abstract[name]
+ if handles is not None:
+ self.assertEqual(handles, r.HandlesEntry(entry, metadata))
+ self.assertRaises(PluginExecutionError,
+ r.HandleEntry, entry, metadata)
+
+ def test_basic(self):
+ """ Test basic Rules usage """
+ self._do_test('basic')
+ self._do_test_failure('unhandled', handles=False)
+
+ def test_priority(self):
+ """ Test that Rules respects priority """
+ self._do_test('priority')
+
+ def test_duplicate(self):
+ """ Test that Rules raises exceptions for duplicate entries """
+ self._do_test_failure('duplicate')
+
+ def test_content(self):
+ """ Test that Rules copies text content from concrete entries """
+ self._do_test('content')
+
+ def test_group(self):
+ """ Test that Rules respects <Group/> tags """
+ self._do_test('group', groups=['foogroup'])
+ self._do_test_failure('group', groups=['bargroup'], handles=False)
+
+ def test_children(self):
+ """ Test that Rules copies child elements from concrete entries """
+ self._do_test('children')
+
+ def test_regex(self):
+ """ Test that Rules handles regular expressions properly """
+ Bcfg2.Options.setup.rules_regex = False
+ self._do_test_failure('regex', handles=False)
+ Bcfg2.Options.setup.rules_regex = True
+ self._do_test('regex')
+ Bcfg2.Options.setup.rules_regex = False
+
+ def test_slash(self):
+ """ Test that Rules handles trailing slashes on Path entries """
+ self._do_test('slash')
+ self._do_test('no_slash')
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestTemplateHelper.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestTemplateHelper.py
index bf9a3ced3..128d6cae5 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestTemplateHelper.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestTemplateHelper.py
@@ -25,7 +25,7 @@ class TestHelperModule(Bcfg2TestCase):
def get_obj(self, path=None):
if path is None:
path = self.path
- return self.test_obj(path, fam=Mock())
+ return self.test_obj(path)
def test__init(self):
hm = self.get_obj()
diff --git a/testsuite/Testsrc/Testlib/TestStatistics.py b/testsuite/Testsrc/Testlib/TestServer/TestStatistics.py
index 496cbac28..bf918ef76 100644
--- a/testsuite/Testsrc/Testlib/TestStatistics.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestStatistics.py
@@ -13,7 +13,7 @@ while path != "/":
path = os.path.dirname(path)
from common import *
-from Bcfg2.Statistics import *
+from Bcfg2.Server.Statistics import *
class TestStatistic(Bcfg2TestCase):
diff --git a/testsuite/Testsrc/Testsbin/bcfg2-crypt/all-basic.xml b/testsuite/Testsrc/Testsbin/bcfg2-crypt/all-basic.xml
new file mode 100644
index 000000000..ce68300b6
--- /dev/null
+++ b/testsuite/Testsrc/Testsbin/bcfg2-crypt/all-basic.xml
@@ -0,0 +1,12 @@
+<Properties>
+ <Foo name="1" encrypted="basic">U2FsdGVkX19C6Cy0nM0mlcGGBjqBMAC+GqyPfLpqgT0=</Foo>
+ <Bar name="1" encrypted="basic">U2FsdGVkX18KUHJTHdrgz3gWtNA5U3g3gq0i/AsdCVE=</Bar>
+ <Foo name="2" encrypted="basic">U2FsdGVkX1+9nUFxmbl8UJh1t5fWo4cQQa5nQm8hVtw=</Foo>
+ <Group name="test">
+ <Bar name="2" encrypted="basic">U2FsdGVkX18tScJs1si9y45NxPkjYj66Ee+TsYDZAd0=</Bar>
+ <Foo name="3" encrypted="basic">U2FsdGVkX19q3USU1cnvgfV8roHeSNSQ2bCMD1CCR3jE0e53aT71ATtqHmfsJfDnTgQ28xbKGZhAwoML8ixXkdkyqnsSF69bnIwebaI4qqYXFA2FWF1Cop3bYEV67m6dSM9BkSluKIcs7VdPRANE71OQnd9P2cQbMig50IkBtuE3El8bnc+n4E0k31NT7ZZJ9s/9FJMHg/AfIjvB4KgMqylHcfT43gGICeq4JYPKIsxYjKq0bzFISPBgztD1++YTdCKbJDtjNJJOlqanB3LsBR3PQt6rliWqqPVT3aLP8BU/gIcGE3oyyce04ULxNGTPqFlWgw2r7RopygqgZbzTgU21thzef7bXRi/NATQpXkM70BdCLwRvRNaC3JrMY/z4k0//QliTiNYPNejpGvwezHf99PTN7VPWMhQyONSpLO905KmEJYRt1BXx8p+72b1Q/1S/QFfzU8JU3MO4yiLFf6kyB6nS+pCA40g/UfwKLQI2Fr6Oi5acOK7SRTXqSmxhI+96TQms6bWmm889BO8QOfiuAI1CvHWEBljPACXydcM9wACjhBvpra41UzVgkzadaUO8yBV0Z0bLVHuyIdI1I78vgrkd99tJvC/AmYazEwM=</Foo>
+ </Group>
+ <Group name="test2">
+ <Bar name="3" encrypted="basic">U2FsdGVkX19aE/IqfkkhgkbhA0i9cb1PYp7tdTmfidg=<Foo name="4" encrypted="basic">U2FsdGVkX1+J4nhfxE5GjwDF1PzOjw2q5e5vrcFZyCQ=</Foo></Bar>
+ </Group>
+</Properties>
diff --git a/testsuite/Testsrc/Testsbin/bcfg2-crypt/basic-des-cbc.crypt b/testsuite/Testsrc/Testsbin/bcfg2-crypt/basic-des-cbc.crypt
new file mode 100644
index 000000000..31aa80e26
--- /dev/null
+++ b/testsuite/Testsrc/Testsbin/bcfg2-crypt/basic-des-cbc.crypt
@@ -0,0 +1 @@
+U2FsdGVkX1/2LyHZkqC9Ri2CeDw45osMNNUwOMraHGD/Z3jsHekaU5w6YlTZ3DUPGJoNKsPiUp0jdheuvFGyv2jmXn4ocIPixcliR16n1lWTv5yE+lleELRRqzRCR+cYjmXJzAHg2L5YJ+/XwZR0tlN5cod81dsHiaVw86K7MU8SlkTuN+QSU24CFucuhi9cXTLt0E7ipIded4J5qlDS2ZZIR/m7LG++VS8jc47S65VHR5VHg6cTQ130b78eG8344LM7i4hVcH1Pm0YOm1Kq3Q+IrmyiPmJKFb1lABoEJHCKAlQ85H5xFdbnXP9FbcZVq9zLmIoB3RsGWLLZobY93FhwXN86VZ2APVn7rzLPZCk8KPqkxGoErUZl4of6eQMvi9Owvf7s6fhkKlG7c03BJ8FtbaKOGob9Y2vM2Vjs322QXnJnGXWZ2ZJLu6+OAdVNUueEhqe12SKIc9Olzv6Gn60tiB5XByqAuY8y6oPm3F+gCdbReh1Lu7fVVGP6brkgZWzoBv/T503nOU/Jw3yRSd9nHwBkiMA3HtjzgWZrXf+TRW5apSAoDAeX70nBR4GtqQO3GozFZgqvr4yzLHo1m3/+mB/svNjeMTbRKp5cKs+47C8rno3+asbVtykrPBnqgC16WRa+fB/juxjpRaEK7dbzhk701lNbJrXKT0S4S5Pdir+SDl1udgpTRiy1MaL9JnwQ5w5du6XfLUH43Q2UiMpNABQAyxHZDsdEcxzj16e7wKXVVguS2mqN8TrWhC1K7ZZ+q8BHZaV1c+R45CwT1nIuGoYzKe0Wsc3WtY8yYTbKJPeJQyL7qSyC/M8cV8RkkDGTgJ/FuRlsm5R89enBFRgb5tmT3S6pG6WxTsxcOyXNwc2VP47dJuetguiEYI8FX6pWbfwSfd1HG7uKkD3QMUYU1YtzCziNLNpag03jd/Ios2q/gR6agKGAzVTSIGeaNwpervlXiHXAdGThWXbqTy+1Zbp4OnEbLGEtksedpTD6Ij+JNFSgyIxDbBdrxvx0EZHf8GeIAiOifRZw3XTxla16oKylv2FnUJMJlwONOyswmwduWaY+0+LYHygzJYyF9YoJVYCASQpz5dll3Z1U/5vKqzrY8/88/SLINPP8nQ6mV1JbnXuRS8CD/LbzI3T0CTDKrsxQkkFsArtdUpYRHBqsPYyuikmG1GGeEtRznkNqcE2EXKoNdIU4Rm6GIK6thes3bYqnp5MK/HLQ2vKkOraSR3IyQJXW7EUVCqZk3M5wFGem+8aEw3sjYljrxNzSAB2EPitnxpBp21Mfu+6SmlGWOXANBNSBG630JzNgfqh31SKrWCw6jyFYrI43UXJEsAdEerfKK/UKBs2rb7Y/DGNF1lM9LDxH2nQ5ByELn/PxxpRdKYoXgsHmPwBHMuXhjPXxY1uw25GZgVVk58GR/l/Q78sRiqiwr2D0rriUTQfLW+bqpxEbdqxJjHa/rGjZQffHC08/xqY9P2FHqcE9VIGH3OdpoM6FKQ/NfBF8FMnkp756JC+nUVgFPTQ5Wn331DlUGyf4MVH6mKskUjOKGCnbt7GnG2K64eFcs0q8k0UK4f8lACtJEA5zSX2qNhPdkpG/yHyzvvLuX2p7j53QyIvX1Nz9k7ZoqD1rjhjDSsfV9C36KORyzqm/MKq8v8YVhEfnGI2UtWM=
diff --git a/testsuite/Testsrc/Testsbin/bcfg2-crypt/basic.crypt b/testsuite/Testsrc/Testsbin/bcfg2-crypt/basic.crypt
new file mode 100644
index 000000000..386544ca8
--- /dev/null
+++ b/testsuite/Testsrc/Testsbin/bcfg2-crypt/basic.crypt
@@ -0,0 +1 @@
+U2FsdGVkX1+fzWyB9BSYjGZTIEw6WGCFJvgWeS5BlGSVcLK6cDuoajcTj47falmJhrCg6uew41tps12UDaGe8Uaw8gzZnqGBCRYYSCazD3mqGsUQDbHdjRdsN5C1uTBlrUt52GuqP0EHEmJrqcmNYCkFw2KM8/RCflpuHkEj3L44PTsuq/TsxUmNLkn01z+U9Y1qK13bl60Gn0T3EYkI1hcO2JzyHW0HHwwe2E9xRB1u56Xj9xL8EozshlafTN2J9iIysG5j7P2dlmGHBQtVgrvbgZ8SycmiIOSenRgpln/Ax9WAwcXXbcvCcSUqxA5CrjfQAkfhMuHhYIoI/f5KJcLKnf1lnOXXlFaQVVLlvMxaw7v67jFOOIJekrphgnIF/VviR0ShP8KFvtTrG56lxHHP2ngZHPg6CxqUtyhCEMp2rES1s0PcAd4xvzUlrgZLvHpphZ0ZVtknDF5hmnFuO/Y1Ebo1PvN2vty1GH/XPcLyzk+Iks92hvYmeOy9fJ+9x+myaHCW/xbwesYrkcUnFvvovres20vM981Dv4GRgO1Yej98ac60wNEu8QigqCJCbVnst4g6+dEhd9xzHp9zsf8cCGcJPWp6BUOdk7Z96uA5UwOsbOgyShlLsp2eWnKAb/5HKx8C8JXdCPD7lFNa4QvrfrH6a1HUugZ7oxU8oQ1mPrg8t6Jf7HyMbBpKdx3BGwnb4yxaOPhpW66OXyFuU0l7yHx5tAzG4ee1HqA8wp5bpvwszCQcLuB5a8s5x+tDs/4pb8bb6f5kV4EnYXza9v8tVCwPegdz8IuluPGjbcO9XM8jjTJ6UDZPIOxNKGoxYUl0RKbAKJ1QC51/d2uNFGcd6j00zs4P5P9G5Pqyy1IMUHqCBpbIO1LI5SgHf9E51NYUjTZJMgxgDfXgo3isDVI39Bob5aNHdjJKWBalU4u5BOWosQ27Du7BmXfAF4CeB4nnilTC/MqvUp7i9RRBfiw9jiPxE1tBtjkqIRDf7k1nGtDWhDcP64ph/Iz5IJ/krafzNew36KAHwYISlDN8KYH5gEewixst7oqiRteCT8D55Es+7645MWHoLa6x/LHOT3sYcXQN+fd8SkjKbR8HBT5upAI+8/WWnU/umodJoyTW62xAhjFQVszj5S6451uTEEf6PnBEpbNaYgQhrUv8bX+WGxNL+Q4XmOUdM8holHyM2Lz7saMG5HlZMlYR6DClQqh5QLG1gmL17+ozARKfQWIXBtsWh5/aTlXVwTDAu8p50abgseNswRWm1QkqtDYxVgsj3qfdtOQo/K/gkHx1dJbkZVCVBRR2DNyKT1DZjU+6q8UJZmZ+JUZ1qcP16y+bc4WOXgBdxvDVWwsR1oe9GokQAHovxTms8VtW5+am37BXpJ+nRo6dVo71yaHXdjf6wQgxzFe9eo1BXypxtx+y5jEuN788d0jwLrZ8bF2KrnbzOinch2WkU+7eFt4pHCClX38hQ/d28q8UOyFEbZp1N9Guwtun3NzX4V0DbcZxIxJDGo45uPLDcT6r9dhAUwrfoM+atTEOM3K4BOBEyxehs7NoZrObWdy0FcrKokCT3iHOqImhVdJDnDYDtfqLFi/iycZ8mUnt6eF7oNLLh/TdB+HgC5bf684YPGYCGznAfjOVE8eIKsG5l6DqGDBxx4fh+Hb563aX/UA8tKrnMhcD/A==
diff --git a/testsuite/Testsrc/Testsbin/bcfg2-crypt/basic2.crypt b/testsuite/Testsrc/Testsbin/bcfg2-crypt/basic2.crypt
new file mode 100644
index 000000000..b26bd91a3
--- /dev/null
+++ b/testsuite/Testsrc/Testsbin/bcfg2-crypt/basic2.crypt
@@ -0,0 +1 @@
+U2FsdGVkX189QQFkAJ2EZErSsJ7h16U71KMeKEHIC803ktBOL0sBOEdWPKN/0G7Jt4mchdGSeBBCFcxGIJ7yGvhThWxgAsCNCZCPbCCGW7NQ8b01+WRzeggfHYuFw49Mwvr9Dqh97z6HelvvoNr90DW/IuxGrJgKzeP2obbdUn0MRuBNx//JjU9lTDRSCU6i5HR/IJvMdYT9iR7icJa0Wjtl6UXjoZXf2azL1jRLrtG2QtMmUI0PQcg9IDlH1Eiosut2LOcmCCThzR96ubQqjRCAggWJqR1x7qe5+Llkq47jPauWuD1RoH7OLTm1EcVRd68ZtfYPHK8tiu3xZqcmnHC5bHedM9hdwiyDqm/qhdnz5cWA4rlWp4VAFTmJzyt4VsgTTXDbCbRHtU0Ml7E36lg4fMekfql1XT+tiNxmoedAWGOXXbvf8rdyH6EZ2uFBhPtX+2kV66XsNBALAizqwVqpw/wdKVkWGRMsi1p8nE4wj6/WcHBd7YYJBig4UNyqdPTsn2dMhKi3GbzgNvkmNo+V6G3IZ0cqy4tBZkBW3izVfN8e5BEUZ6BR4V8uC/NvTSd7fcJSJZROw/RR2zdTo7qDqgzz27aZrPghDfmLYB26VGn56geSVwEFraDeaUZxa17UiBLtGYPb8rZRQkn0ACsfHu8r2PVt6p/W6oVnZ/05icvP2NjGtWWbzXRpHZEw/5R+cRsfTgi2HpWpJwpqt6Y+dxXb5mpNkwL3tM/p9i1+LKm+jxknkmtCW8rxeHseuvX3WIcyAP7JEBzv76xliKBQP0lpvM53fNcgsJvzgphnwUGAlht+2Y9ZBlKSdGyrft+K4hxYWRcCWMg8+s/VA1VkHl3+JOzwmgfGFx3eVcZnul1Xq58BGWRmVXo0PfSo0VCZuhVuegF2Odqfrx2w57CwKESxDkKGt7h8m7CexJbVHzawJlC5ZbP6iLHCf2rHXIcD/K4Qjl/rbDLvMrHpazSOpaIckYdXFeA3UglEFQ4m4o8OEYJ1rH8z3jQt4A+3L4+8+8tzeTIfnVgA2ouqs00XcoFoPSSvbzZstawJubBR6/pOwYjJAAJTVMPw08X/8qM1NJHSvxgkDlWrm8uUfgGbG6ftkCKlbEuJmWLH2I74zHa0/2j8VtcoHjFkbQc7Slg/pM0IC55ZJhgaZ74qsGJaGz5FpdYx8Fz+riqBTGZA7LpVzsYGl/f4Cmtvf6v0f2pCypb1bMWp+IEn2w7StGVd6oSuc9+wgBi9RWAmlB27MkBvKnx+IuUPpKtUSUG+YM3mFUl9f5A0spMdTiDPdh8Wegqx5S2PLKt61YvoMS8vSQTGxMf3A3WhykYGrFxq8l1zKcZfD7x4oPj398rfBGQPpemuiTuDJeGtsKta8ERgLw1bgrtEu6PWvEf4bSCLYa1x7RkrOihDPyn+jP0v/1Cb7+eQR33tVwNYdN7ZNRzvbn7JHKuci8DrZU2961aA+7t1UMjSaJ06RG4pm120pQckRi3vALxh583KlqpG5a8+DAHq46h16W7PC9IbDdb5YRgAo0QwOnIdFLj4EKt5GefOspnfOWM5gROU33l4J2n+5IsH8d0eOTGXlIVmyye3fXMTTI248DMchBR/xUKisujZZsvjFutxYYX/RGXoIHllmycpAsqnMBPNj6edjPruAqnyDzHpS88Y3eBSv446GA==
diff --git a/testsuite/Testsrc/Testsbin/bcfg2-crypt/bogus-forced.xml b/testsuite/Testsrc/Testsbin/bcfg2-crypt/bogus-forced.xml
new file mode 100644
index 000000000..d5d0eb8d9
--- /dev/null
+++ b/testsuite/Testsrc/Testsbin/bcfg2-crypt/bogus-forced.xml
@@ -0,0 +1,5 @@
+<Bogus>
+ <!-- This is not a real properties file; we use it to test forcing
+ bcfg2-crypt to treat it like one -->
+ <Test encrypted="basic">U2FsdGVkX1+uFQUijBDQpGBdTroNS6nl8lPUSeHcWJw=</Test>
+</Bogus>
diff --git a/testsuite/Testsrc/Testsbin/bcfg2-crypt/bogus.xml b/testsuite/Testsrc/Testsbin/bcfg2-crypt/bogus.xml
new file mode 100644
index 000000000..8c83afa69
--- /dev/null
+++ b/testsuite/Testsrc/Testsbin/bcfg2-crypt/bogus.xml
@@ -0,0 +1,5 @@
+<Bogus>
+ <!-- This is not a real properties file; we use it to test forcing
+ bcfg2-crypt to treat it like one -->
+ <Test encrypted="basic">some text</Test>
+</Bogus>
diff --git a/testsuite/Testsrc/Testsbin/bcfg2-crypt/complex.crypt b/testsuite/Testsrc/Testsbin/bcfg2-crypt/complex.crypt
new file mode 100644
index 000000000..cd4ada4bf
--- /dev/null
+++ b/testsuite/Testsrc/Testsbin/bcfg2-crypt/complex.crypt
@@ -0,0 +1 @@
+U2FsdGVkX1+pB62f8PRmn7i+qzc0nVvezv4sL6KXA4SaTWGvai48caajA480b//AfSkSaLK9C5pDFk0hf0HvoSK/qFdEL/3sChswE2EXJqcZCQxSpexQPhd4t/ES3m40PPO+WmK3AemjvNSv9oQdIXPlgwboVBoBfJ61onPqdQSrDtheDRsaNzOA9kYe5dttl7PwKz9UTj8Ds7rRXvrFdXvmX/KvwWHxQvHmz7RzFfMzBQ7AS+yKo4JPVMSA6peBQRsmu8n/c+G4q0MXntmPRfSELyxX2BG9kIUKJEmCgJHVOnN6Yd/wseKQWbWFut/vB+tq+83yTQKJmEtDx1EAupXv3oBNDpDR2wjLsijvAQCaBkvalHyLEC8/KlC3P6VMUObiaHOMIxcMX9wH5C2jGYCTZ+tlAnzM9RfKkXPEq0sXbTpRy0laRWfV6c39PDFgTE3qBy79Reh4V+YfqG0X2jFR3bbcG1ZFc/QL20X+6LvfP/cxf/zepZ1sa9yJmAw386xtt7tDohdZBmvwOgueqbHhlRnf2hXggSUBHLxspAKsy8kyHzSFBy71qzmXo4j3C96MFf/SCCDJsX1P2yEdny7hKxHKqvQBFR2JlN+m0DFt5rnhiWAkLbpJsBdy66hZ9dmWa3IfKhO9EDkAFf5ts7ZXPgimpdj/A7IjtD19FmBLZ/N29spA+InK661iHwc3HVJ14AorA9gOyeCW6p3GBS/BzENALEDrKk3O9w0Dk4pRcrlRgvEK+HZF6CDVV5jh188Lt3eWReCfBQ9Cwoj/sDY16WNTsqJnA9f+FIUW7WxSpUA5g3bk2IR3pfnzXLfxFpUUGy7v7y7EvOMOWH27550NKtXOszcS72rRzwf4jze7mN51Wt1n9iKKpmoUZNwJ6E6vaU2o/NyuWrWHMWPtTusdQsVMkm2J3TddvMFp6u8mTMIj5ypMuz8+9f2QgLjWrCTY0llVHQfQvELgSSNoNBMfpFKqZt0qk4Zoo0a/CRFj+wEv0zOwF8A0HAtIXrnF4zeiLjDlN4lMrR67hKOuHNLpR6hqkCUSo2blf3d3AiwN6YfLHNuVywuUHINVeoyzls1q6WXuLQuO3HOdpMOWrp4tA6PoDAia36qT8+WhK/hrzinEbiu0Wvd9uv7Ie/veAGBcqW+4SOu0kJqQLgnQfGXvUoxs5TvgbTsrs8TW9CtihR0u0vuuec4El6VEA0xVDKbyI1H2ac3X44qEXwVwWqC2JJdy9O2uNgEQhGah+qLiumvKQElZngMw/ovE76rysF8eO7z1Hkt45raOcodUcAnvTgvGYrVwPKa3a4ShBpT/94IjbsMQ/S9dsnACCps7/TGtqgS96XsookFX8m9x2oycJnpiF59+UlcyyxY6e+vAkl11eIwH/niEpl3JzGKGUi5rs3y7MiLR12ZAoI5R9xBpSY8nm9OGNN5lO242cDnstdjhd7Qz2bR8/KDFQtIVLgxhKrRaUnQr0CFBQ/bW0RS0E1SdFssixvvPohlAXoBQssVOUPHVXbk9On9k8ebQ9n9lhd6L2dK0HydVUFLr0vtVCTv049aLcI3yQ8a6TrOxtVy793hzwY0UZC8X5pjYqT6V/ddyRWzJQRvXnK5KzL4KHWSOXTvwam8TYMp3TdBEi8G+Raydi2kVSeGYEjdjkGSfGgw5kRoeDqkworeZSA==
diff --git a/testsuite/Testsrc/Testsbin/bcfg2-crypt/plaintext b/testsuite/Testsrc/Testsbin/bcfg2-crypt/plaintext
new file mode 100644
index 000000000..71fa9654e
--- /dev/null
+++ b/testsuite/Testsrc/Testsbin/bcfg2-crypt/plaintext
@@ -0,0 +1,9 @@
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla in elit arcu. Morbi interdum fermentum magna non molestie. Curabitur imperdiet, mi eget ultrices porttitor, dolor arcu dictum purus, eu tristique felis felis eget leo. Suspendisse dignissim laoreet velit, id bibendum leo. Etiam faucibus lorem nunc, eget laoreet tortor feugiat at. Fusce at ornare tellus. Donec dui neque, fermentum quis ante ut, sodales commodo magna. Proin nec malesuada risus, ac consectetur mi. Praesent pharetra eleifend lacinia. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec odio metus, dictum vel commodo quis, tincidunt in ligula. Aenean et orci non arcu lobortis ultricies. Ut ipsum nisl, luctus sed porta nec, vestibulum pharetra tellus.
+
+ Praesent consectetur condimentum nisl ut cursus. Etiam aliquam nisi
+ dolor. Mauris aliquet condimentum neque, sodales laoreet lectus
+ venenatis ac. Morbi mattis justo odio, ac fringilla leo egestas
+ ut. Integer nec sapien pulvinar, ultrices nulla id, posuere
+ magna. Quisque in tincidunt sem, sed vehicula orci. Nulla blandit,
+ nisi vel cursus semper, nibh metus consequat purus, ac ullamcorper
+ dolor lorem vitae ligula. Maecenas non consectetur nibh.
diff --git a/testsuite/Testsrc/Testsbin/bcfg2-crypt/plaintext-all.xml b/testsuite/Testsrc/Testsbin/bcfg2-crypt/plaintext-all.xml
new file mode 100644
index 000000000..2dccaa51c
--- /dev/null
+++ b/testsuite/Testsrc/Testsbin/bcfg2-crypt/plaintext-all.xml
@@ -0,0 +1,12 @@
+<Properties>
+ <Foo name="1" encrypted="basic">U2FsdGVkX19CZCt2ydtozka/HuG9Iay3Dpxs/pR7byM=</Foo>
+ <Bar name="1" encrypted="complex">U2FsdGVkX18RmlLRK6CSIww69iuUTAb1xOkA/2dZw84=</Bar>
+ <Foo name="2" encrypted="complex">U2FsdGVkX1+h5sBn5Ms1FXe88o69Wc0tE99Nuck++tQ=</Foo>
+ <Group name="test">
+ <Bar name="2" encrypted="basic">U2FsdGVkX1/NcWDYbvU1fUWry44xvFxYQXodBoTs/Ek=</Bar>
+ <Foo name="3" encrypted="basic">U2FsdGVkX1+02mmcZw+h/QkC+Qr48bjy198xcivfopvaK64xzBe25fEBADvIG7Qab+BxZdZAPWFgX3toBVQFVjQ6M6zf1lrNeciK39LSDj4v3mTIg1/gvew8TUGeQtkrU/xo8ShEAiExma6ILf7Qq6PTc5IdBfuB85bn5YtU1tN4YTiUbK3/DIkTRJI+8YI4GbhFBKeaqMkau5498YdwhxpE0LB0sxTK3Bjw23nwOfcLLXH0uYux1JCxDgYJ9Zalx0qPUthrNnEq2mR9R11lZGmRQD8ToN0/7eS8NkZs3j5TgefbzNdpK7yThbXHFPNwuD6I1AwhQ5oJ//iOkVGpAMXvdPkEZCgKthXnze/X99J0MphTq6oD7XGrY+Sj5EwVzv8X9Mux96QtFylCIGhNllkCAqb3Mzmsr7ZIEmauAr+eTkuRASjJr7XsQKSL5hoLFtF/vKnzTx6YjVETrbXkczZUhA2n7C3HF1OYAozPZmd2WTF7/15jcWCKZB517dfKr5GC1q10NlbiujEUfb/8JnVRg9JfK5r6oXcdfxbODbLchzU+/h2sRRjSVdN6wcXrX+bVMJG9P6cLiPR3oruBUHf/dbZXg06Mp1bqazbOpJY=</Foo>
+ </Group>
+ <Group name="test2">
+ <Bar name="3" encrypted="basic">U2FsdGVkX1/keWAAgSOnVvhoEDWzmRQWaf3mxOy749o=<Foo name="4" encrypted="basic">U2FsdGVkX1+O10Id9f9FUcavHi8JaQWVNlWm/jwQ4f4=</Foo></Bar>
+ </Group>
+</Properties>
diff --git a/testsuite/Testsrc/Testsbin/bcfg2-crypt/plaintext-xpath.xml b/testsuite/Testsrc/Testsbin/bcfg2-crypt/plaintext-xpath.xml
new file mode 100644
index 000000000..4bcec1474
--- /dev/null
+++ b/testsuite/Testsrc/Testsbin/bcfg2-crypt/plaintext-xpath.xml
@@ -0,0 +1,12 @@
+<Properties>
+ <Foo name="1" encrypted="basic">U2FsdGVkX18bAwhcMtr8J02ztT8kBjdCjae9lYnbsRY=</Foo>
+ <Bar name="1" encrypted="complex">Some text</Bar>
+ <Foo name="2" encrypted="complex">U2FsdGVkX19+Yq+VwbAfNGUHtnB7hy74V7Fvz0GHsqA=</Foo>
+ <Group name="test">
+ <Bar name="2" encrypted="basic">Some text</Bar>
+ <Foo name="3" encrypted="basic">U2FsdGVkX1+A3f6lIoFvCNaC6/ctbOLqT0z/YCJ+FkeyTgAnmU1/wk7FlPxOaPPkI2iRfEbNK2sNtUS0rQ8TYT3gIRO3qyrUNIcHaYfGerAZN3Xg9F3CsaL1NQjHxSKyJLdTmdB/1m0AQ3jw13n1eNrokGHF6HU6bD6TIJVFmds126ucOg56Xh+3ffOUukh2EwlBxnvGC/CDQluFixL1MY74xFd5mY5iDcJG9o5qUtjKmiOrtEAXFuM49JLciDHtMjQ2wbX/9lGek7U2Y05I2vU8BGtD3jh/Pt+17Vql80UrNHqVnWx247sxgYmkJIaworCTNowU2KsWEQj46E1bzAXEUVdGF65ltIXIK75KozHf8msKuVFwQDYCJ+lXRZgIygqcZ5glAyjW1WxyigxSFpRfVcZfiHp7d52JfBCU66367j7DvEnAJAuvL7jufJSavd6RxaEGGB3KGAMpz8NQxPy6i2s5RkY5V8eiqUOHsnZN6zHPgkZ90a+dokllLbH+HSYGU26sevJL4TupDCkz2/sRasmBB8fBAF5PGOI+UC7vXncbvpsMLsILFoUYtyWrDZ9cygOElEmWpVJSeECAA09iOhyaXN5rN/tyqkt3+ao=</Foo>
+ </Group>
+ <Group name="test2">
+ <Bar name="3" encrypted="basic">Some text<Foo name="4" encrypted="basic">U2FsdGVkX1+cSl37JVEVIEV+bqVBlMGQnZdZWsjHPME=</Foo></Bar>
+ </Group>
+</Properties>
diff --git a/testsuite/Testsrc/Testsbin/bcfg2-crypt/plaintext.xml b/testsuite/Testsrc/Testsbin/bcfg2-crypt/plaintext.xml
new file mode 100644
index 000000000..45d9941c8
--- /dev/null
+++ b/testsuite/Testsrc/Testsbin/bcfg2-crypt/plaintext.xml
@@ -0,0 +1,18 @@
+<Properties>
+ <Foo name="1" encrypted="basic">Some text</Foo>
+ <Bar name="1" encrypted="complex">Some text</Bar>
+ <Foo name="2" encrypted="complex">Some text</Foo>
+ <Group name="test">
+ <Bar name="2" encrypted="basic">Some text</Bar>
+ <Foo name="3" encrypted="basic">Praesent consectetur condimentum nisl ut cursus. Etiam aliquam nisi
+dolor. Mauris aliquet condimentum neque, sodales laoreet lectus
+venenatis ac. Morbi mattis justo odio, ac fringilla leo egestas
+ut. Integer nec sapien pulvinar, ultrices nulla id, posuere
+magna. Quisque in tincidunt sem, sed vehicula orci. Nulla blandit,
+nisi vel cursus semper, nibh metus consequat purus, ac ullamcorper
+dolor lorem vitae ligula. Maecenas non consectetur nibh.</Foo>
+ </Group>
+ <Group name="test2">
+ <Bar name="3" encrypted="basic">Some text<Foo name="4" encrypted="basic">Nested text</Foo></Bar>
+ </Group>
+</Properties>
diff --git a/testsuite/Testsrc/Testsbin/bcfg2-crypt/plaintext2.xml b/testsuite/Testsrc/Testsbin/bcfg2-crypt/plaintext2.xml
new file mode 100644
index 000000000..fa63330f0
--- /dev/null
+++ b/testsuite/Testsrc/Testsbin/bcfg2-crypt/plaintext2.xml
@@ -0,0 +1,18 @@
+<Properties>
+ <Foo name="1" encrypted="basic">Some text</Foo>
+ <Bar name="1" encrypted="basic">Some text</Bar>
+ <Foo name="2" encrypted="basic">Some text</Foo>
+ <Group name="test">
+ <Bar name="2" encrypted="basic">Some text</Bar>
+ <Foo name="3" encrypted="basic">Praesent consectetur condimentum nisl ut cursus. Etiam aliquam nisi
+dolor. Mauris aliquet condimentum neque, sodales laoreet lectus
+venenatis ac. Morbi mattis justo odio, ac fringilla leo egestas
+ut. Integer nec sapien pulvinar, ultrices nulla id, posuere
+magna. Quisque in tincidunt sem, sed vehicula orci. Nulla blandit,
+nisi vel cursus semper, nibh metus consequat purus, ac ullamcorper
+dolor lorem vitae ligula. Maecenas non consectetur nibh.</Foo>
+ </Group>
+ <Group name="test2">
+ <Bar name="3" encrypted="basic">Some text<Foo name="4" encrypted="basic">Nested text</Foo></Bar>
+ </Group>
+</Properties>
diff --git a/testsuite/Testsrc/Testsbin/test_bcfg2_crypt.py b/testsuite/Testsrc/Testsbin/test_bcfg2_crypt.py
new file mode 100644
index 000000000..3eee4415f
--- /dev/null
+++ b/testsuite/Testsrc/Testsbin/test_bcfg2_crypt.py
@@ -0,0 +1,390 @@
+# -*- coding: utf-8 -*-
+import os
+import sys
+import shutil
+import difflib
+import tempfile
+import lxml.etree
+import Bcfg2.Options
+from Bcfg2.Compat import StringIO, b64decode, u_str
+from mock import Mock, MagicMock, patch
+
+# 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)
+from common import *
+
+try:
+ from Bcfg2.Server.Encryption import CLI
+ HAS_CRYPTO = True
+except ImportError:
+ HAS_CRYPTO = False
+
+
+class TestEncryption(Bcfg2TestCase):
+ cfg_plaintext = None
+ known_files = None
+ basedir = None
+
+ @classmethod
+ def setUpClass(cls):
+ basedir = os.path.join(os.path.dirname(__file__), "bcfg2-crypt")
+ cls.basedir = tempfile.mkdtemp()
+ for fname in os.listdir(basedir):
+ shutil.copy(os.path.join(basedir, fname), cls.basedir)
+ cls.known_files = os.listdir(cls.basedir)
+ cls.cfg_plaintext = open(os.path.join(cls.basedir, "plaintext")).read()
+
+ @classmethod
+ def tearDownClass(cls):
+ shutil.rmtree(cls.basedir)
+
+ @skipUnless(HAS_CRYPTO, "Encryption libraries not found")
+ def setUp(self):
+ set_setup_default("lax_decryption", False)
+
+ def set_options(self):
+ Bcfg2.Options.setup.algorithm = "aes_256_cbc"
+ Bcfg2.Options.setup.passphrases = dict(
+ basic="basic",
+ complex="1234567890əùíÿł¢€ñû⸘" * 10)
+
+ def tearDown(self):
+ # clean up stray files created by tests
+ for fname in os.listdir(self.basedir):
+ if fname not in self.known_files:
+ os.unlink(os.path.join(self.basedir, fname))
+
+ def assertExists(self, fname):
+ fpath = os.path.join(self.basedir, fname)
+ self.assertTrue(os.path.exists(fpath),
+ "%s does not exist" % fpath)
+
+ def assertNotExists(self, fname):
+ fpath = os.path.join(self.basedir, fname)
+ self.assertFalse(os.path.exists(fpath),
+ "%s exists, but shouldn't" % fpath)
+
+ def assertFilesEqual(self, fname1, fname2):
+ self.assertExists(fname1)
+ self.assertExists(fname2)
+ contents1 = open(os.path.join(self.basedir, fname1)).read().strip()
+ contents2 = open(os.path.join(self.basedir, fname2)).read().strip()
+ diff = "\n".join(
+ difflib.unified_diff(contents1.splitlines(),
+ contents2.splitlines(),
+ fname1, fname2)).replace("\n\n", "\n")
+ self.assertEqual(contents1, contents2,
+ "Contents of %s and %s do not match:\n%s" %
+ (fname1, fname2, diff))
+
+ def assertFilesNotEqual(self, fname1, fname2):
+ self.assertExists(fname1)
+ self.assertExists(fname2)
+ self.assertNotEqual(
+ open(os.path.join(self.basedir, fname1)).read(),
+ open(os.path.join(self.basedir, fname2)).read(),
+ "Contents of %s and %s are unexpectedly identical")
+
+ def _is_encrypted(self, data):
+ """ Pretty crappy check for whether or not data is encrypted:
+ just see if it's a valid base64-encoded string whose contents
+ start with "Salted__". But without decrypting, which rather
+ begs the question in a set of crypto unit tests, I'm not sure
+ how to do a better test."""
+ try:
+ return b64decode(data).startswith("Salted__")
+ except UnicodeDecodeError:
+ # decoded base64, resulting value contained non-ASCII text
+ return True
+ except TypeError:
+ # couldn't decode base64
+ return False
+
+ def assertIsEncrypted(self, data):
+ if not self._is_encrypted(data):
+ self.fail("Data is not encrypted: %s" % data)
+
+ def assertNotEncrypted(self, data):
+ if self._is_encrypted(data):
+ self.fail("Data is unexpectedly encrypted: %s" % data)
+
+ def _decrypt(self, cli, outfile, expected=None):
+ self.set_options()
+ cli.run()
+ if expected is None:
+ self.assertExists(outfile)
+ actual = open(os.path.join(self.basedir, outfile)).read()
+ self.assertEqual(self.cfg_plaintext, actual)
+ self.assertNotEncrypted(actual)
+ else:
+ self.assertFilesEqual(outfile, expected)
+
+ def _encrypt(self, cli, outfile, original=None):
+ self.set_options()
+ cli.run()
+ if original is None:
+ self.assertExists(outfile)
+ actual = open(os.path.join(self.basedir, outfile)).read()
+ self.assertNotEqual(self.cfg_plaintext, actual)
+ self.assertIsEncrypted(actual)
+ else:
+ self.assertFilesNotEqual(outfile, original)
+
+ def _cfg_decrypt(self, opts, encrypted):
+ if encrypted.endswith(".crypt"):
+ decrypted = encrypted[:-6]
+ else:
+ self.fail("Could not determine decrypted filename for %s" %
+ encrypted)
+ cli = CLI(opts + [os.path.join(self.basedir, encrypted)])
+ self._decrypt(cli, decrypted)
+
+ def _cfg_encrypt(self, opts, plaintext):
+ cli = CLI(opts + [os.path.join(self.basedir, plaintext)])
+ self._encrypt(cli, plaintext + ".crypt")
+
+ def _props_decrypt(self, opts, encrypted, expected):
+ test = os.path.join(self.basedir, "test.xml")
+ shutil.copy(os.path.join(self.basedir, encrypted), test)
+ cli = CLI(opts + [test])
+ self._decrypt(cli, "test.xml", expected)
+ try:
+ xdata = lxml.etree.parse(test)
+ except:
+ self.fail("Could not parse decrypted Properties file: %s" %
+ sys.exc_info()[1])
+ for el in xdata.iter():
+ if el.tag is not lxml.etree.Comment and el.text.strip():
+ self.assertNotEncrypted(el.text)
+
+ def _props_encrypt(self, opts, plaintext, check_all=True):
+ test = os.path.join(self.basedir, "test.xml")
+ shutil.copy(os.path.join(self.basedir, plaintext), test)
+ cli = CLI(opts + [test])
+ self._encrypt(cli, "test.xml", plaintext)
+ try:
+ xdata = lxml.etree.parse(test)
+ except:
+ self.fail("Could not parse encrypted Properties file: %s" %
+ sys.exc_info()[1])
+ if check_all:
+ for el in xdata.iter():
+ if el.tag is not lxml.etree.Comment and el.text.strip():
+ self.assertIsEncrypted(el.text)
+
+ def test_decrypt_cfg(self):
+ """ Decrypt a Cfg file """
+ self._cfg_decrypt(["--decrypt", "--cfg", "-p", "basic"],
+ "basic.crypt")
+
+ def test_decrypt_cfg_complex(self):
+ """ Decrypt a Cfg file with a passphrase with special characters """
+ self._cfg_decrypt(["--decrypt", "--cfg", "-p", "complex"],
+ "complex.crypt")
+
+ def test_decrypt_cfg_algorithm(self):
+ """ Decrypt a Cfg file with a non-default algorithm """
+ # this can't be done with self._cfg_decrypt or even
+ # self._decrypt because we have to set the algorithm after
+ # other options are set, but before the decrypt is performed
+ cli = CLI(["--decrypt", "--cfg", "-p", "basic",
+ os.path.join(self.basedir, "basic-des-cbc.crypt")])
+ self.set_options()
+ Bcfg2.Options.setup.algorithm = "des_cbc"
+ cli.run()
+ self.assertExists("basic-des-cbc")
+ actual = open(os.path.join(self.basedir, "basic-des-cbc")).read()
+ self.assertEqual(self.cfg_plaintext, actual)
+ self.assertNotEncrypted(actual)
+
+ def test_cfg_auto_passphrase(self):
+ """ Discover the passphrase to decrypt a Cfg file"""
+ self._cfg_decrypt(["--decrypt", "--cfg"], "complex.crypt")
+
+ def test_cfg_auto_mode(self):
+ """ Discover whether to encrypt or decrypt a Cfg file """
+ self._cfg_decrypt(["--cfg", "-p", "basic"], "basic.crypt")
+ self._cfg_encrypt(["--cfg", "-p", "basic"], "plaintext")
+
+ def test_cfg_auto_type(self):
+ """ Discover a file is a Cfg file """
+ self._cfg_decrypt(["--decrypt", "-p", "basic"], "basic.crypt")
+ self._cfg_encrypt(["--encrypt", "-p", "basic"], "plaintext")
+
+ def test_cfg_multiple(self):
+ """ Decrypt multiple Cfg files """
+ cli = CLI(["--decrypt", "--cfg", "-p", "basic",
+ os.path.join(self.basedir, "basic.crypt"),
+ os.path.join(self.basedir, "basic2.crypt")])
+ self.set_options()
+ cli.run()
+ self.assertExists("basic")
+ self.assertExists("basic2")
+ actual1 = open(os.path.join(self.basedir, "basic")).read()
+ actual2 = open(os.path.join(self.basedir, "basic2")).read()
+ self.assertEqual(self.cfg_plaintext, actual1)
+ self.assertEqual(self.cfg_plaintext, actual2)
+ self.assertNotEncrypted(actual1)
+ self.assertNotEncrypted(actual2)
+
+ def test_cfg_auto_all(self):
+ """ Discover all options to encrypt/decrypt Cfg files """
+ self._cfg_decrypt([], "complex.crypt")
+ self._cfg_encrypt(["-p", "basic"], "plaintext")
+
+ def test_cfg_stdout(self):
+ """ Decrypt a Cfg file to stdout """
+ cli = CLI(["--decrypt", "--cfg", "-p", "basic", "--stdout",
+ os.path.join(self.basedir, "basic.crypt")])
+ self.set_options()
+ old_stdout = sys.stdout
+ sys.stdout = StringIO()
+ cli.run()
+ output = sys.stdout.getvalue()
+ sys.stdout = old_stdout
+
+ self.assertNotExists("basic")
+ self.assertEqual(self.cfg_plaintext.strip(), output.strip())
+ self.assertNotEncrypted(output)
+
+ def test_encrypt_cfg(self):
+ """ Encrypt a Cfg file """
+ self._cfg_encrypt(["--encrypt", "--cfg", "-p", "basic"], "plaintext")
+ os.rename(os.path.join(self.basedir, "plaintext.crypt"),
+ os.path.join(self.basedir, "test.crypt"))
+ self._cfg_decrypt(["--decrypt", "--cfg", "-p", "basic"],
+ "test.crypt")
+
+ def test_encrypt_props_as_cfg(self):
+ """ Encrypt an XML file as a Cfg file """
+ cli = CLI(["--encrypt", "--cfg", "-p", "basic",
+ os.path.join(self.basedir, "plaintext.xml")])
+ self._encrypt(cli, "plaintext.xml.crypt", "plaintext.xml")
+
+ os.rename(os.path.join(self.basedir, "plaintext.xml.crypt"),
+ os.path.join(self.basedir, "test.xml.crypt"))
+ cli = CLI(["--decrypt", "--cfg", "-p", "basic",
+ os.path.join(self.basedir, "test.xml.crypt")])
+ self._decrypt(cli, "test.xml", "plaintext.xml")
+
+ def test_cfg_remove(self):
+ """ Encrypt and remove a Cfg file """
+ test = os.path.join(self.basedir, "test")
+ shutil.copy(os.path.join(self.basedir, "plaintext"), test)
+ self._cfg_encrypt(["--encrypt", "--remove", "--cfg", "-p", "basic"],
+ test)
+ self.assertNotExists("test")
+
+ def test_decrypt_props(self):
+ """ Decrypt a Properties file """
+ self._props_decrypt(["--decrypt", "--properties", "-p", "basic"],
+ "all-basic.xml", "plaintext2.xml")
+
+ def test_props_decrypt_multiple_passphrases(self):
+ """ Decrypt a Properties file with multiple passphrases"""
+ self._props_decrypt(["--decrypt", "--properties"],
+ "plaintext-all.xml", "plaintext.xml")
+
+ def test_props_decrypt_mixed(self):
+ """ Decrypt a Properties file with mixed encrypted content"""
+ self._props_decrypt(["--decrypt", "--properties"],
+ "plaintext-xpath.xml", "plaintext.xml")
+
+ def test_props_decrypt_bogus(self):
+ """ Decrypt a malformed Properties file """
+ self._props_decrypt(["--decrypt", "--properties"],
+ "bogus-forced.xml", "bogus.xml")
+
+ def test_props_decrypt_auto_type(self):
+ """ Discover an encrypted file is a Properties file """
+ self._props_decrypt(["--decrypt"],
+ "all-basic.xml", "plaintext2.xml")
+
+ def test_props_decrypt_auto_mode(self):
+ """ Discover whether to encrypt or decrypt an encrypted Properties file """
+ self._props_decrypt(["--properties"],
+ "all-basic.xml", "plaintext2.xml")
+
+ def test_props_decrypt_auto_all(self):
+ """ Discover all options to decrypt a Properties file """
+ self._props_decrypt([], "all-basic.xml", "plaintext2.xml")
+
+ def test_props_encrypt_cli_passphrase(self):
+ """ Encrypt a Properties file with passphrase on the CLI"""
+ self._props_encrypt(["--encrypt", "--properties", "-p", "basic"],
+ "plaintext2.xml")
+ os.rename(os.path.join(self.basedir, "test.xml"),
+ os.path.join(self.basedir, "encrypted.xml"))
+ self._props_decrypt(["--decrypt", "--properties", "-p", "basic"],
+ "encrypted.xml", "plaintext2.xml")
+
+ def test_props_encrypt_file_passphrase(self):
+ """ Encrypt a Properties file with passphrase in the file """
+ self._props_encrypt(["--encrypt", "--properties"], "plaintext2.xml")
+ os.rename(os.path.join(self.basedir, "test.xml"),
+ os.path.join(self.basedir, "encrypted.xml"))
+ self._props_decrypt(["--decrypt", "--properties"],
+ "encrypted.xml", "plaintext2.xml")
+
+ def test_props_encrypt_multiple_passphrases(self):
+ """ Encrypt a Properties file with multiple passphrases """
+ self._props_encrypt(["--encrypt", "--properties"], "plaintext.xml")
+ os.rename(os.path.join(self.basedir, "test.xml"),
+ os.path.join(self.basedir, "encrypted.xml"))
+ self._props_decrypt(["--decrypt", "--properties"],
+ "encrypted.xml", "plaintext.xml")
+
+ def test_props_encrypt_xpath(self):
+ """ Encrypt a Properties file with --xpath """
+ test = os.path.join(self.basedir, "test.xml")
+ self._props_encrypt(["--encrypt", "--properties", "--xpath", "//Foo"],
+ "plaintext.xml", check_all=False)
+ xdata = lxml.etree.parse(test)
+ for el in xdata.iter():
+ if el.tag is not lxml.etree.Comment and el.text.strip():
+ if el.tag == "Foo":
+ self.assertIsEncrypted(el.text)
+ else:
+ self.assertNotEncrypted(el.text)
+
+ os.rename(test, os.path.join(self.basedir, "encrypted.xml"))
+ self._props_decrypt(["--decrypt", "--properties"],
+ "encrypted.xml", "plaintext.xml")
+
+ def test_props_encrypt_bogus(self):
+ """ Decrypt a malformed Properties file """
+ self._props_encrypt(["--encrypt", "--properties"], "bogus.xml")
+ os.rename(os.path.join(self.basedir, "test.xml"),
+ os.path.join(self.basedir, "encrypted.xml"))
+ self._props_decrypt(["--decrypt", "--properties"],
+ "encrypted.xml", "bogus.xml")
+
+ def test_props_encrypt_auto_type(self):
+ """ Discover if a file is a Properties file """
+ self._props_encrypt(["--encrypt"], "plaintext2.xml")
+ os.rename(os.path.join(self.basedir, "test.xml"),
+ os.path.join(self.basedir, "encrypted.xml"))
+ self._props_decrypt(["--decrypt"],
+ "encrypted.xml", "plaintext2.xml")
+
+ def test_props_encrypt_auto_mode(self):
+ """ Discover whether to encrypt or decrypt a Properties file """
+ self._props_encrypt(["--properties"], "plaintext2.xml")
+ os.rename(os.path.join(self.basedir, "test.xml"),
+ os.path.join(self.basedir, "encrypted.xml"))
+ self._props_decrypt(["--properties"],
+ "encrypted.xml", "plaintext2.xml")
+
+ def test_props_encrypt_auto_all(self):
+ """ Discover all options to encrypt a Properties file """
+ self._props_encrypt([], "plaintext.xml")
+ os.rename(os.path.join(self.basedir, "test.xml"),
+ os.path.join(self.basedir, "encrypted.xml"))
+ self._props_decrypt([], "encrypted.xml", "plaintext.xml")
diff --git a/testsuite/Testsrc/test_code_checks.py b/testsuite/Testsrc/test_code_checks.py
index 98df358e7..17fac4fe4 100644
--- a/testsuite/Testsrc/test_code_checks.py
+++ b/testsuite/Testsrc/test_code_checks.py
@@ -49,11 +49,10 @@ contingent_checks = {
# perform only error checking on the listed files
error_checks = {
- "sbin": ["bcfg2-build-reports"],
- "lib/Bcfg2": ["Proxy.py", "SSLServer.py", "Reporting"],
- "lib/Bcfg2/Server": ["Reports", "SchemaUpdater"],
- "lib/Bcfg2/Server/Admin": ["Compare.py",
- "Snapshots.py"],
+ "lib/Bcfg2": ["Reporting"],
+ "lib/Bcfg2/Client": ["Proxy.py"],
+ "lib/Bcfg2/Server": ["Reports", "SchemaUpdater", "SSLServer.py"],
+ "lib/Bcfg2/Server/Admin": ["Compare.py"],
"lib/Bcfg2/Client/Tools": ["OpenCSW.py",
"Blast.py",
"FreeBSDInit.py",
@@ -67,21 +66,13 @@ error_checks = {
# perform no checks at all on the listed files
no_checks = {
"lib/Bcfg2/Client/Tools": ["APT.py", "RPM.py", "rpmtools.py"],
- "lib/Bcfg2/Server": ["Snapshots", "Hostbase"],
"lib/Bcfg2": ["manage.py"],
"lib/Bcfg2/Server/Reports": ["manage.py"],
- "lib/Bcfg2/Server/Plugins": ["Account.py",
- "Base.py",
- "Editor.py",
- "Hostbase.py",
- "Snapshots.py",
- "Statistics.py",
- "TCheetah.py",
- "TGenshi.py"],
+ "lib/Bcfg2/Server/Plugins": ["Base.py"],
}
if sys.version_info < (2, 6):
# multiprocessing core requires py2.6
- no_checks['lib/Bcfg2/Server'].append('MultiprocessingCore.py')
+ no_checks['lib/Bcfg2/Server'] = ['MultiprocessingCore.py']
try:
any
diff --git a/testsuite/common.py b/testsuite/common.py
index 7471795a6..5a08f8db5 100644
--- a/testsuite/common.py
+++ b/testsuite/common.py
@@ -12,15 +12,13 @@ import os
import re
import sys
import codecs
-import unittest
import lxml.etree
+import Bcfg2.Options
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 = "/"
+try:
+ from unittest import skip, skipIf, skipUnless, TestCase
+except ImportError:
+ from unittest2 import skip, skipIf, skipUnless, TestCase
#: The XInclude namespace name
XI_NAMESPACE = "http://www.w3.org/2001/XInclude"
@@ -34,19 +32,43 @@ inPy3k = False
if sys.hexversion >= 0x03000000:
inPy3k = True
+
+#: A function to set a default config option if it's not already set
+def set_setup_default(option, value=None):
+ if not hasattr(Bcfg2.Options.setup, option):
+ setattr(Bcfg2.Options.setup, option, value)
+
+Bcfg2.Options.Parser.unit_test = True
+
try:
- from django.core.management import setup_environ
+ import django.conf
has_django = True
- os.environ['DJANGO_SETTINGS_MODULE'] = "Bcfg2.settings"
-
- import Bcfg2.settings
- Bcfg2.settings.DATABASE_NAME = \
- os.path.join(os.path.dirname(os.path.abspath(__file__)), "test.sqlite")
- Bcfg2.settings.DATABASES['default']['NAME'] = Bcfg2.settings.DATABASE_NAME
+ set_setup_default("db_engine", "sqlite3")
+ set_setup_default("db_name",
+ os.path.join(os.path.dirname(os.path.abspath(__file__)),
+ "test.sqlite"))
+ set_setup_default("db_user")
+ set_setup_default("db_password")
+ set_setup_default("db_host")
+ set_setup_default("db_port")
+ set_setup_default("db_opts", dict())
+ set_setup_default("db_schema")
+ set_setup_default("timezone")
+ set_setup_default("web_debug", False)
+ set_setup_default("web_prefix")
+
+ import Bcfg2.DBSettings
+ Bcfg2.DBSettings.finalize_django_config()
except ImportError:
has_django = False
+#: The path to the Bcfg2 specification root for the tests. Using the
+#: root directory exposes a lot of potential problems with building
+#: paths.
+datastore = "/"
+
+set_setup_default("repository", datastore)
try:
from mock import call
@@ -91,180 +113,12 @@ else:
return codecs.unicode_escape_decode(s)[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:
- return skip(msg)
-
-
-def _count_diff_all_purpose(actual, expected):
- '''Returns list of (cnt_act, cnt_exp, elem) triples where the
- counts differ'''
- # elements need not be hashable
- s, t = list(actual), list(expected)
- m, n = len(s), len(t)
- NULL = object()
- result = []
- for i, elem in enumerate(s):
- if elem is NULL:
- continue
- cnt_s = cnt_t = 0
- for j in range(i, m):
- if s[j] == elem:
- cnt_s += 1
- s[j] = NULL
- for j, other_elem in enumerate(t):
- if other_elem == elem:
- cnt_t += 1
- t[j] = NULL
- if cnt_s != cnt_t:
- diff = (cnt_s, cnt_t, elem)
- result.append(diff)
-
- for i, elem in enumerate(t):
- if elem is NULL:
- continue
- cnt_t = 0
- for j in range(i, n):
- if t[j] == elem:
- cnt_t += 1
- t[j] = NULL
- diff = (0, cnt_t, elem)
- result.append(diff)
- return result
-
-
-def _assertion(predicate, default_msg=None):
- @wraps(predicate)
- def inner(self, *args, **kwargs):
- if 'msg' in kwargs:
- msg = kwargs['msg']
- del kwargs['msg']
- else:
- try:
- msg = default_msg % args
- except TypeError:
- # message passed as final (non-keyword) argument?
- msg = args[-1]
- args = args[:-1]
- assert predicate(*args, **kwargs), msg
- return inner
-
-
-def _regex_matches(val, regex):
- if hasattr(regex, 'search'):
- return regex.search(val)
- else:
- return re.search(regex, val)
-
-
-class Bcfg2TestCase(unittest.TestCase):
+class Bcfg2TestCase(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.
+ :class:`unittest.TestCase`. This class adds
+ :func:`assertXMLEqual`, a useful assertion method given all the
+ XML used by Bcfg2.
"""
- 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)
-
- if differences:
- standardMsg = 'Element counts were not equal:\n'
- lines = ['First has %d, Second has %d: %r' % diff
- for diff in differences]
- diffMsg = '\n'.join(lines)
- standardMsg += diffMsg
- if msg is None:
- msg = standardMsg
- else:
- msg = "%s : %s" % (standardMsg, msg)
- self.fail(msg)
-
- if not hasattr(unittest.TestCase, "assertRegexpMatches"):
- # Some versions of TestCase in Py3k seem to lack
- # assertRegexpMatches, but have the other convenience methods.
- assertRegexpMatches = _assertion(lambda s, r: _regex_matches(s, r),
- "%s does not contain /%s/")
-
- if not hasattr(unittest.TestCase, "assertNotRegexpMatches"):
- # Some versions of TestCase in Py3k seem to lack
- # assertNotRegexpMatches even though they have
- # assertRegexpMatches
- assertNotRegexpMatches = \
- _assertion(lambda s, r: not _regex_matches(s, r),
- "%s contains /%s/")
-
- if not hasattr(unittest.TestCase, "assertIn"):
- # versions of TestCase before python 2.7 and python 3.1 lacked
- # a lot of the really handy convenience methods, so we provide
- # them -- at least the easy ones and the ones we use.
- assertIs = _assertion(lambda a, b: a is b, "%s is not %s")
- assertIsNot = _assertion(lambda a, b: a is not b, "%s is %s")
- assertIsNone = _assertion(lambda x: x is None, "%s is not None")
- assertIsNotNone = _assertion(lambda x: x is not None, "%s is None")
- assertIn = _assertion(lambda a, b: a in b, "%s is not in %s")
- assertNotIn = _assertion(lambda a, b: a not in b, "%s is in %s")
- assertIsInstance = _assertion(isinstance, "%s is not instance of %s")
- assertNotIsInstance = _assertion(lambda a, b: not isinstance(a, b),
- "%s is instance of %s")
- assertGreater = _assertion(lambda a, b: a > b,
- "%s is not greater than %s")
- assertGreaterEqual = _assertion(lambda a, b: a >= b,
- "%s is not greater than or equal to %s")
- assertLess = _assertion(lambda a, b: a < b, "%s is not less than %s")
- assertLessEqual = _assertion(lambda a, b: a <= b,
- "%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. """
if msg is None:
@@ -308,15 +162,18 @@ class Bcfg2TestCase(unittest.TestCase):
class DBModelTestCase(Bcfg2TestCase):
""" Test case class for Django database models """
models = []
+ __test__ = False
@skipUnless(has_django, "Django not found, skipping")
def test_syncdb(self):
""" Create the test database and sync the schema """
- setup_environ(Bcfg2.settings)
- import django.core.management
- django.core.management.call_command("syncdb", interactive=False,
- verbosity=0)
- self.assertTrue(os.path.exists(Bcfg2.settings.DATABASE_NAME))
+ if self.models:
+ import django.core.management
+ django.core.management.call_command("syncdb", interactive=False,
+ verbosity=0)
+ self.assertTrue(
+ os.path.exists(
+ django.conf.settings.DATABASES['default']['NAME']))
@skipUnless(has_django, "Django not found, skipping")
def test_cleandb(self):
diff --git a/testsuite/install.sh b/testsuite/install.sh
index 9de5b8c6d..50b91a4d2 100755
--- a/testsuite/install.sh
+++ b/testsuite/install.sh
@@ -6,9 +6,12 @@ pip install -r testsuite/requirements.txt --use-mirrors
PYVER=$(python -c 'import sys;print(".".join(str(v) for v in sys.version_info[0:2]))')
+if [[ ${PYVER:0:1} == "2" && $PYVER != "2.7" ]]; then
+ pip install --use-mirrors unittest2
+fi
+
if [[ "$WITH_OPTIONAL_DEPS" == "yes" ]]; then
- pip install --use-mirrors genshi PyYAML pyinotify boto 'django<1.5' \
- pylibacl
+ pip install --use-mirrors PyYAML pyinotify boto pylibacl 'django<1.5'
easy_install https://fedorahosted.org/released/python-augeas/python-augeas-0.4.1.tar.gz
if [[ ${PYVER:0:1} == "2" ]]; then
# django supports py3k, but South doesn't, and the django bits
diff --git a/testsuite/pylintrc.conf b/testsuite/pylintrc.conf
index 94904877b..1d3ba8c88 100644
--- a/testsuite/pylintrc.conf
+++ b/testsuite/pylintrc.conf
@@ -154,7 +154,7 @@ ignore-mixin-members=yes
# List of classes names for which member attributes should not be checked
# (useful for classes with attributes dynamically set).
-ignored-classes=ForeignKey,Interaction,git.cmd.Git
+ignored-classes=ForeignKey,Interaction,git.cmd.Git,argparse.Namespace,Namespace
# When zope mode is activated, add a predefined set of Zope acquired attributes
# to generated-members.
diff --git a/testsuite/requirements.txt b/testsuite/requirements.txt
index 898249389..d7eaa1ac9 100644
--- a/testsuite/requirements.txt
+++ b/testsuite/requirements.txt
@@ -5,3 +5,5 @@ sphinx
pylint<1.0
pep8
python-daemon
+genshi
+argparse