From f31a4e80aaaafca79eb6c808c81a6067f0c493b8 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Wed, 26 Sep 2012 16:29:50 -0700 Subject: Add a new example test that will patch utils and os functions so that they can be 'retargeted' to a temporary directory, which allows us the ability to run a full set of cloud-init stages. Neat things: 1. All cloud-init code is unchanged (as long as it goes through the utils functions for most functionality) 2. Allows for a natural way to setup a temporary directory then patch the new directory as the new 'root' and then run cloud-init stages and then check the contents of what was placed as desired. --- tests/unittests/helpers.py | 106 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) (limited to 'tests/unittests/helpers.py') diff --git a/tests/unittests/helpers.py b/tests/unittests/helpers.py index d0f09e70..d7aebdee 100644 --- a/tests/unittests/helpers.py +++ b/tests/unittests/helpers.py @@ -1,8 +1,42 @@ import os +import mocker + from mocker import MockerTestCase from cloudinit import helpers as ch +from cloudinit import util + +import shutil + +# Makes the old path start +# with new base instead of whatever +# it previously had +def rebase_path(old_path, new_base): + if old_path.startswith(new_base): + # Already handled... + return old_path + # Retarget the base of that path + # to the new base instead of the + # old one... + path = os.path.join(new_base, old_path.lstrip("/")) + path = os.path.abspath(path) + return path + + +# Can work on anything that takes a path +# as first argument +def retarget_many_wrapper(new_base, am, old_func): + def wrapper(*args, **kwds): + n_args = list(args) + nam = am + if am == -1: + nam = len(n_args) + for i in range(0, nam): + path = args[i] + n_args[i] = rebase_path(path, new_base) + return old_func(*n_args, **kwds) + return wrapper class ResourceUsingTestCase(MockerTestCase): @@ -40,3 +74,75 @@ class ResourceUsingTestCase(MockerTestCase): 'templates_dir': self.resourceLocation(), }) return cp + + +class FilesystemMockingTestCase(ResourceUsingTestCase): + def __init__(self, methodName="runTest"): + ResourceUsingTestCase.__init__(self, methodName) + self.patched_funcs = [] + + def replicateTestRoot(self, example_root, target_root): + real_root = self.resourceLocation() + real_root = os.path.join(real_root, 'roots', example_root) + for (dir_path, dirnames, filenames) in os.walk(real_root): + real_path = dir_path + make_path = rebase_path(real_path[len(real_root):], target_root) + util.ensure_dir(make_path) + for f in filenames: + real_path = util.abs_join(real_path, f) + make_path = util.abs_join(make_path, f) + shutil.copy(real_path, make_path) + + def tearDown(self): + self.restore() + ResourceUsingTestCase.tearDown(self) + + def restore(self): + for (mod, f, func) in self.patched_funcs: + setattr(mod, f, func) + self.patched_funcs = [] + + def patchUtils(self, new_root): + patch_funcs = { + util: [('write_file', 1), + ('load_file', 1), + ('ensure_dir', 1), + ('chmod', 1), + ('delete_dir_contents', 1), + ('del_file', 1), + ('sym_link', -1)], + } + for (mod, funcs) in patch_funcs.items(): + for (f, am) in funcs: + func = getattr(mod, f) + trap_func = retarget_many_wrapper(new_root, am, func) + setattr(mod, f, trap_func) + self.patched_funcs.append((mod, f, func)) + + # Handle subprocess calls + func = getattr(util, 'subp') + + def nsubp(*args, **kwargs): + return ('', '') + + setattr(util, 'subp', nsubp) + self.patched_funcs.append((util, 'subp', func)) + + def null_func(*args, **kwargs): + return None + + for f in ['chownbyid', 'chownbyname']: + func = getattr(util, f) + setattr(util, f, null_func) + self.patched_funcs.append((util, f, func)) + + def patchOS(self, new_root): + patch_funcs = { + os.path: ['isfile', 'exists', 'islink', 'isdir'], + } + for (mod, funcs) in patch_funcs.items(): + for f in funcs: + func = getattr(mod, f) + trap_func = retarget_many_wrapper(new_root, 1, func) + setattr(mod, f, trap_func) + self.patched_funcs.append((mod, f, func)) -- cgit v1.2.3 From 4a5d5a044db9e77b931fb4df93f7c01b02021f44 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Wed, 26 Sep 2012 16:40:07 -0700 Subject: Fixup some pylint warnings. --- tests/unittests/helpers.py | 8 +++----- tests/unittests/test_filters/test_launch_index.py | 12 ++++++++++-- tests/unittests/test_runs/test_simple_run.py | 14 ++++++-------- 3 files changed, 19 insertions(+), 15 deletions(-) (limited to 'tests/unittests/helpers.py') diff --git a/tests/unittests/helpers.py b/tests/unittests/helpers.py index d7aebdee..7b829f24 100644 --- a/tests/unittests/helpers.py +++ b/tests/unittests/helpers.py @@ -1,7 +1,5 @@ import os -import mocker - from mocker import MockerTestCase from cloudinit import helpers as ch @@ -84,7 +82,7 @@ class FilesystemMockingTestCase(ResourceUsingTestCase): def replicateTestRoot(self, example_root, target_root): real_root = self.resourceLocation() real_root = os.path.join(real_root, 'roots', example_root) - for (dir_path, dirnames, filenames) in os.walk(real_root): + for (dir_path, _dirnames, filenames) in os.walk(real_root): real_path = dir_path make_path = rebase_path(real_path[len(real_root):], target_root) util.ensure_dir(make_path) @@ -122,13 +120,13 @@ class FilesystemMockingTestCase(ResourceUsingTestCase): # Handle subprocess calls func = getattr(util, 'subp') - def nsubp(*args, **kwargs): + def nsubp(*_args, **_kwargs): return ('', '') setattr(util, 'subp', nsubp) self.patched_funcs.append((util, 'subp', func)) - def null_func(*args, **kwargs): + def null_func(*_args, **_kwargs): return None for f in ['chownbyid', 'chownbyname']: diff --git a/tests/unittests/test_filters/test_launch_index.py b/tests/unittests/test_filters/test_launch_index.py index 7ca7cbb6..1e9b9053 100644 --- a/tests/unittests/test_filters/test_launch_index.py +++ b/tests/unittests/test_filters/test_launch_index.py @@ -1,6 +1,14 @@ import copy +import os +import sys -import helpers as th +top_dir = os.path.join(os.path.dirname(__file__), os.pardir, "helpers.py") +top_dir = os.path.abspath(top_dir) +if os.path.exists(top_dir): + sys.path.insert(0, os.path.dirname(top_dir)) + + +import helpers import itertools @@ -18,7 +26,7 @@ def count_messages(root): return am -class TestLaunchFilter(th.ResourceUsingTestCase): +class TestLaunchFilter(helpers.ResourceUsingTestCase): def assertCounts(self, message, expected_counts): orig_message = copy.deepcopy(message) diff --git a/tests/unittests/test_runs/test_simple_run.py b/tests/unittests/test_runs/test_simple_run.py index 33dd3ca2..7f646b54 100644 --- a/tests/unittests/test_runs/test_simple_run.py +++ b/tests/unittests/test_runs/test_simple_run.py @@ -1,6 +1,3 @@ -from mocker import MockerTestCase - -import mocker import sys import os @@ -19,7 +16,7 @@ from cloudinit import stages from cloudinit.settings import (PER_INSTANCE) -class TestSimpleRunDistro(helpers.FilesystemMockingTestCase): +class TestSimpleRun(helpers.FilesystemMockingTestCase): def _patchIn(self, root): self.restore() self.patchOS(root) @@ -76,13 +73,14 @@ class TestSimpleRunDistro(helpers.FilesystemMockingTestCase): initer.update() self.assertTrue(os.path.islink("var/lib/cloud/instance")) - (ran, results) = initer.cloudify().run('consume_userdata', - initer.consume_userdata, - args=[PER_INSTANCE], - freq=PER_INSTANCE) + initer.cloudify().run('consume_userdata', + initer.consume_userdata, + args=[PER_INSTANCE], + freq=PER_INSTANCE) mods = stages.Modules(initer) (which_ran, failures) = mods.run_section('cloud_init_modules') + self.assertTrue(len(failures) == 0) self.assertTrue(os.path.exists('/etc/blah.ini')) self.assertIn('write-files', which_ran) contents = util.load_file('/etc/blah.ini') -- cgit v1.2.3 From b541f738616349a028c5e54754ea83e439d82734 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Wed, 26 Sep 2012 16:42:15 -0700 Subject: Adjust comment. --- tests/unittests/helpers.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'tests/unittests/helpers.py') diff --git a/tests/unittests/helpers.py b/tests/unittests/helpers.py index 7b829f24..d5df580b 100644 --- a/tests/unittests/helpers.py +++ b/tests/unittests/helpers.py @@ -22,8 +22,7 @@ def rebase_path(old_path, new_base): return path -# Can work on anything that takes a path -# as first argument +# Can work on anything that takes a path as arguments def retarget_many_wrapper(new_base, am, old_func): def wrapper(*args, **kwds): n_args = list(args) -- cgit v1.2.3 From a17a69c35c1de0a6bd6f054f76d3da9e4a9c5364 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Sat, 10 Nov 2012 10:15:16 -0800 Subject: Sudoers.d creation cleanups + tests. --- cloudinit/distros/__init__.py | 20 +++++++++++------ tests/unittests/helpers.py | 1 + tests/unittests/test_distros/test_generic.py | 32 +++++++++++++++++++++++++--- tests/unittests/test_runs/test_merge_run.py | 2 +- 4 files changed, 45 insertions(+), 10 deletions(-) (limited to 'tests/unittests/helpers.py') diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py index 2d01efc3..d2cb0a8b 100644 --- a/cloudinit/distros/__init__.py +++ b/cloudinit/distros/__init__.py @@ -283,8 +283,10 @@ class Distro(object): # Ensure the dir is included and that # it actually exists as a directory sudoers_contents = '' + base_exists = False if os.path.exists(sudo_base): sudoers_contents = util.load_file(sudo_base) + base_exists = True found_include = False for line in sudoers_contents.splitlines(): line = line.strip() @@ -299,18 +301,24 @@ class Distro(object): found_include = True break if not found_include: - sudoers_contents += "\n#includedir %s\n" % (path) try: - if not os.path.exists(sudo_base): + if not base_exists: + lines = [('# See sudoers(5) for more information' + ' on "#include" directives:'), '', + '# Added by cloud-init', + "#includedir %s" % (path), ''] + sudoers_contents = "\n".join(lines) util.write_file(sudo_base, sudoers_contents, 0440) else: - with open(sudo_base, 'a') as f: - f.write(sudoers_contents) - LOG.debug("added '#includedir %s' to %s" % (path, sudo_base)) + lines = ['', '# Added by cloud-init', + "#includedir %s" % (path), ''] + sudoers_contents = "\n".join(lines) + util.append_file(sudo_base, sudoers_contents) + LOG.debug("Added '#includedir %s' to %s" % (path, sudo_base)) except IOError as e: util.logexc(LOG, "Failed to write %s" % sudo_base, e) raise e - util.ensure_dir(path, 0755) + util.ensure_dir(path, 0750) def write_sudo_rules(self, user, diff --git a/tests/unittests/helpers.py b/tests/unittests/helpers.py index 2c5dcad2..e8080668 100644 --- a/tests/unittests/helpers.py +++ b/tests/unittests/helpers.py @@ -103,6 +103,7 @@ class FilesystemMockingTestCase(ResourceUsingTestCase): def patchUtils(self, new_root): patch_funcs = { util: [('write_file', 1), + ('append_file', 1), ('load_file', 1), ('ensure_dir', 1), ('chmod', 1), diff --git a/tests/unittests/test_distros/test_generic.py b/tests/unittests/test_distros/test_generic.py index 2df4c2f0..704699b5 100644 --- a/tests/unittests/test_distros/test_generic.py +++ b/tests/unittests/test_distros/test_generic.py @@ -1,6 +1,9 @@ -from mocker import MockerTestCase - from cloudinit import distros +from cloudinit import util + +from tests.unittests import helpers + +import os unknown_arch_info = { 'arches': ['default'], @@ -27,7 +30,7 @@ gpmi = distros._get_package_mirror_info # pylint: disable=W0212 gapmi = distros._get_arch_package_mirror_info # pylint: disable=W0212 -class TestGenericDistro(MockerTestCase): +class TestGenericDistro(helpers.FilesystemMockingTestCase): def return_first(self, mlist): if not mlist: @@ -52,6 +55,29 @@ class TestGenericDistro(MockerTestCase): # Make a temp directoy for tests to use. self.tmp = self.makeDir() + def test_sudoers_ensure_new(self): + cls = distros.fetch("ubuntu") + d = cls("ubuntu", {}, None) + self.patchOS(self.tmp) + self.patchUtils(self.tmp) + d.ensure_sudo_dir("/b") + contents = util.load_file("/etc/sudoers") + self.assertIn("includedir /b", contents) + self.assertTrue(os.path.isdir("/b")) + + def test_sudoers_ensure_append(self): + cls = distros.fetch("ubuntu") + d = cls("ubuntu", {}, None) + self.patchOS(self.tmp) + self.patchUtils(self.tmp) + util.write_file("/etc/sudoers", "josh, josh\n") + d.ensure_sudo_dir("/b") + contents = util.load_file("/etc/sudoers") + self.assertIn("includedir /b", contents) + self.assertTrue(os.path.isdir("/b")) + self.assertIn("josh", contents) + self.assertEquals(2, contents.count("josh")) + def test_arch_package_mirror_info_unknown(self): """for an unknown arch, we should get back that with arch 'default'.""" arch_mirrors = gapmi(package_mirrors, arch="unknown") diff --git a/tests/unittests/test_runs/test_merge_run.py b/tests/unittests/test_runs/test_merge_run.py index 04c03730..36de97ae 100644 --- a/tests/unittests/test_runs/test_merge_run.py +++ b/tests/unittests/test_runs/test_merge_run.py @@ -7,7 +7,7 @@ from cloudinit import stages from cloudinit import util -class TestSimpleRun(helpers.FilesystemMockingTestCase): +class TestMergeRun(helpers.FilesystemMockingTestCase): def _patchIn(self, root): self.restore() self.patchOS(root) -- cgit v1.2.3 From 5c63171d2ac45a1931130d14e33579beb6902fdb Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Tue, 13 Nov 2012 15:49:46 -0800 Subject: Create a utility testcase class that fixes some of the 2.6 missing pieces - Add a helper testcase class that can add additional features into the unit test class as we need for features that are useful to have which starts with features that are missing including assertIn and assertNotIn LP: #1078473 --- tests/unittests/helpers.py | 23 ++++++++++++++++++++++ .../test_handler/test_handler_power_state.py | 6 +++--- 2 files changed, 26 insertions(+), 3 deletions(-) (limited to 'tests/unittests/helpers.py') diff --git a/tests/unittests/helpers.py b/tests/unittests/helpers.py index e8080668..92540b0c 100644 --- a/tests/unittests/helpers.py +++ b/tests/unittests/helpers.py @@ -1,4 +1,6 @@ import os +import sys +import unittest from mocker import MockerTestCase @@ -7,6 +9,27 @@ from cloudinit import util import shutil +# Handle how 2.6 doesn't have the assertIn or assertNotIn +_PY_VER = sys.version_info +_PY_MAJOR, _PY_MINOR = _PY_VER[0:2] +if (_PY_MAJOR, _PY_MINOR) <= (2, 6): + # For now add these on, taken from python 2.7 + slightly adjusted + class TestCase(unittest.TestCase): + def assertIn(self, member, container, msg=None): + if member not in container: + standardMsg = '%r not found in %r' % (member, container) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertNotIn(self, member, container, msg=None): + if member in container: + standardMsg = '%r unexpectedly found in %r' + standardMsg = standardMsg % (member, container) + self.fail(self._formatMessage(msg, standardMsg)) + +else: + class TestCase(unittest.TestCase): + pass + # Makes the old path start # with new base instead of whatever diff --git a/tests/unittests/test_handler/test_handler_power_state.py b/tests/unittests/test_handler/test_handler_power_state.py index 1149fedc..f6e37fa5 100644 --- a/tests/unittests/test_handler/test_handler_power_state.py +++ b/tests/unittests/test_handler/test_handler_power_state.py @@ -1,9 +1,9 @@ -from unittest import TestCase - from cloudinit.config import cc_power_state_change as psc +from tests.unittests import helpers as t_help + -class TestLoadPowerState(TestCase): +class TestLoadPowerState(t_help.TestCase): def setUp(self): super(self.__class__, self).setUp() -- cgit v1.2.3