Merge ~powersj/cloud-init:fix-centos-unittests into cloud-init:master

Proposed by Joshua Powers on 2017-04-27
Status: Merged
Approved by: Scott Moser on 2017-05-16
Approved revision: da2f7adc71a2d3b8fc7a7c70868474e26732a986
Merged at revision: 6edf0c72ce11e64627d3db6a0c4fd74a81039ed4
Proposed branch: ~powersj/cloud-init:fix-centos-unittests
Merge into: cloud-init:master
Diff against target: 194 lines (+88/-31)
4 files modified
cloudinit/config/cc_apt_configure.py (+12/-7)
tests/unittests/test_handler/test_handler_apt_configure_sources_list_v3.py (+73/-21)
tests/unittests/test_handler/test_handler_yum_add_repo.py (+2/-2)
tox.ini (+1/-1)
Reviewer Review Type Date Requested Status
Ryan Harper 2017-04-27 Approve on 2017-05-05
Server Team CI bot continuous-integration Approve on 2017-05-03
Review via email: mp+323351@code.launchpad.net

Commit Message

unittests: fix unittests run on centos

Apt related tests were broken when running on centos becasue apt
is not available. This fixes the unit test, with a small re-work of apt_configure.

Also in 'tox -e centos6' only run nose on tests/unittests as tests/
also contain integration tests that should not be run.

To post a comment you must log in.
Ryan Harper (raharper) wrote :

Thanks for taking this.

1) let's use @mock.patch.object decorators on the helper function for the common items (write, isfile, etc)

2) instead of testing all possible paths in the helper function and relying on the config_on_empty list, pull out the asserts to the test_* callers; they're setting up the scenario (distro=debian, cfg=XXX); they're in the best position to assert a specific path through the code.

3) Let's add a distro=ubuntu system_is_snappy=True case

4) Let's add distro=centos which should fall down the 'no apt binary' path

review: Needs Fixing
Joshua Powers (powersj) wrote :

If I use decorators on the helper function, but try to move asserts to the callers how is that going to work? From looking at this more it looks like every caller function would start to look like test_apt_v3_srcl_custom and have its own mocked objects and not even use the helper function.

Scott Moser (smoser) wrote :

You could have the helper function resturn the mocks and the result of the call. There is a helper wrap_and_call which does something similar, but at the moment does not return the mocks.

One option is to modify that to return the mocks also.

On April 28, 2017 2:30:40 PM EDT, Joshua Powers <email address hidden> wrote:
>If I use decorators on the helper function, but try to move asserts to
>the callers how is that going to work? From looking at this more it
>looks like every caller function would start to look like
>test_apt_v3_srcl_custom and have its own mocked objects and not even
>use the helper function.
>--
>https://code.launchpad.net/~powersj/cloud-init/+git/cloud-init/+merge/323351
>You are subscribed to branch cloud-init:master.

c7a3952... by Joshua Powers on 2017-05-01

Moving assert logic, using mock as context manager since using default arguments

1d23c8c... by Joshua Powers on 2017-05-01

fix

8e899e0... by Joshua Powers on 2017-05-01

revert fix

ed61b61... by Joshua Powers on 2017-05-01

slight change

Joshua Powers (powersj) wrote :

Update:

* I used mock context managers over the use of the decorators. I was unable to even move a single one up without getting some odd errors. I can show you want I had, but we now have two default arguments, so having non-default arguments follow default arguments won't work in any case.

* Like Scott suggested I returned the mocks (never done that before, but like how it worked) and now the callers are running the asserts.

* Added a centos/rhel test

* Added a is_snappy variable and test case for ubuntu_snappy. Not sure on the unit test on this one other than to verify it wasn't called, similar to centos/rhel.

Ryan Harper (raharper) wrote :

I like this a lot better, thanks for reworking this.

Couple of items to address.

review: Needs Fixing
d5c58ed... by Joshua Powers on 2017-05-02

Revamping is_snappy test and variable names

Joshua Powers (powersj) wrote :

Because we already mock _should_configure_on_empty_apt I decided to pull the snappy check into its own separate function that is simple. If you disagree let me know.

Also updated all variable names to something more verbose.

Ryan Harper (raharper) wrote :

Generally looks good, couple questions below.

Joshua Powers (powersj) wrote :

I'll push changes shortly. Reply below.

da2f7ad... by Joshua Powers on 2017-05-03

ryans changes

Joshua Powers (powersj) wrote :

I forgot comments get removed when I push, so:

1) I changed the line breaks from using / to use ()
2) I updated to using assert_called_with as suggested
3) +commands = nosetests {posargs:tests/unittests} is required as in the tests folder we have integration tests that contain unit tests that should not be run during unit testing.

Lasted push includes these changes/fixes.

Ryan Harper (raharper) :
review: Approve
Joshua Powers (powersj) wrote :

Confirmed on fresh centos 6 & 7 LXD as working. This one is ready to go.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/config/cc_apt_configure.py b/cloudinit/config/cc_apt_configure.py
2index f6885d7..177cbcf 100644
3--- a/cloudinit/config/cc_apt_configure.py
4+++ b/cloudinit/config/cc_apt_configure.py
5@@ -282,16 +282,21 @@ def handle(name, ocfg, cloud, log, _):
6 apply_apt(cfg, cloud, target)
7
8
9+def _should_configure_on_empty_apt():
10+ # if no config was provided, should apt configuration be done?
11+ if util.system_is_snappy():
12+ return False, "system is snappy."
13+ if not (util.which('apt-get') or util.which('apt')):
14+ return False, "no apt commands."
15+ return True, "Apt is available."
16+
17+
18 def apply_apt(cfg, cloud, target):
19 # cfg is the 'apt' top level dictionary already in 'v3' format.
20 if not cfg:
21- # no config was provided. If apt configuration does not seem
22- # necessary on this system, then return.
23- if util.system_is_snappy():
24- LOG.debug("Nothing to do: No apt config and running on snappy")
25- return
26- if not (util.which('apt-get') or util.which('apt')):
27- LOG.debug("Nothing to do: No apt config and no apt commands")
28+ should_config, msg = _should_configure_on_empty_apt()
29+ if not should_config:
30+ LOG.debug("Nothing to do: No apt config and %s", msg)
31 return
32
33 LOG.debug("handling apt config: %s", cfg)
34diff --git a/tests/unittests/test_handler/test_handler_apt_configure_sources_list_v3.py b/tests/unittests/test_handler/test_handler_apt_configure_sources_list_v3.py
35index 24e4523..1ca915b 100644
36--- a/tests/unittests/test_handler/test_handler_apt_configure_sources_list_v3.py
37+++ b/tests/unittests/test_handler/test_handler_apt_configure_sources_list_v3.py
38@@ -121,39 +121,82 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase):
39 myds.metadata.update(metadata)
40 return cloud.Cloud(myds, paths, {}, mydist, None)
41
42- def _apt_source_list(self, cfg, expected, distro):
43- "_apt_source_list - Test rendering from template (generic)"
44-
45+ def _apt_source_list(self, distro, cfg, cfg_on_empty=False):
46+ """_apt_source_list - Test rendering from template (generic)"""
47 # entry at top level now, wrap in 'apt' key
48 cfg = {'apt': cfg}
49 mycloud = self._get_cloud(distro)
50- with mock.patch.object(util, 'write_file') as mockwf:
51+
52+ with mock.patch.object(util, 'write_file') as mock_writefile:
53 with mock.patch.object(util, 'load_file',
54- return_value=MOCKED_APT_SRC_LIST) as mocklf:
55+ return_value=MOCKED_APT_SRC_LIST
56+ ) as mock_loadfile:
57 with mock.patch.object(os.path, 'isfile',
58- return_value=True) as mockisfile:
59- with mock.patch.object(util, 'rename'):
60- cc_apt_configure.handle("test", cfg, mycloud,
61- LOG, None)
62-
63- # check if it would have loaded the distro template
64- mockisfile.assert_any_call(
65- ('/etc/cloud/templates/sources.list.%s.tmpl' % distro))
66- mocklf.assert_any_call(
67- ('/etc/cloud/templates/sources.list.%s.tmpl' % distro))
68- # check expected content in result
69- mockwf.assert_called_once_with('/etc/apt/sources.list', expected,
70- mode=0o644)
71+ return_value=True) as mock_isfile:
72+ cfg_func = ('cloudinit.config.cc_apt_configure.' +
73+ '_should_configure_on_empty_apt')
74+ with mock.patch(cfg_func,
75+ return_value=(cfg_on_empty, "test")
76+ ) as mock_shouldcfg:
77+ cc_apt_configure.handle("test", cfg, mycloud, LOG,
78+ None)
79+
80+ return mock_writefile, mock_loadfile, mock_isfile, mock_shouldcfg
81
82 def test_apt_v3_source_list_debian(self):
83 """test_apt_v3_source_list_debian - without custom sources or parms"""
84 cfg = {}
85- self._apt_source_list(cfg, EXPECTED_BASE_CONTENT, 'debian')
86+ distro = 'debian'
87+ expected = EXPECTED_BASE_CONTENT
88+
89+ mock_writefile, mock_load_file, mock_isfile, mock_shouldcfg = (
90+ self._apt_source_list(distro, cfg, cfg_on_empty=True))
91+
92+ template = '/etc/cloud/templates/sources.list.%s.tmpl' % distro
93+ mock_writefile.assert_called_once_with('/etc/apt/sources.list',
94+ expected, mode=0o644)
95+ mock_load_file.assert_called_with(template)
96+ mock_isfile.assert_any_call(template)
97+ self.assertEqual(1, mock_shouldcfg.call_count)
98
99 def test_apt_v3_source_list_ubuntu(self):
100 """test_apt_v3_source_list_ubuntu - without custom sources or parms"""
101 cfg = {}
102- self._apt_source_list(cfg, EXPECTED_BASE_CONTENT, 'ubuntu')
103+ distro = 'ubuntu'
104+ expected = EXPECTED_BASE_CONTENT
105+
106+ mock_writefile, mock_load_file, mock_isfile, mock_shouldcfg = (
107+ self._apt_source_list(distro, cfg, cfg_on_empty=True))
108+
109+ template = '/etc/cloud/templates/sources.list.%s.tmpl' % distro
110+ mock_writefile.assert_called_once_with('/etc/apt/sources.list',
111+ expected, mode=0o644)
112+ mock_load_file.assert_called_with(template)
113+ mock_isfile.assert_any_call(template)
114+ self.assertEqual(1, mock_shouldcfg.call_count)
115+
116+ def test_apt_v3_source_list_ubuntu_snappy(self):
117+ """test_apt_v3_source_list_ubuntu_snappy - without custom sources or
118+ parms"""
119+ cfg = {'apt': {}}
120+ mycloud = self._get_cloud('ubuntu')
121+
122+ with mock.patch.object(util, 'write_file') as mock_writefile:
123+ with mock.patch.object(util, 'system_is_snappy',
124+ return_value=True) as mock_issnappy:
125+ cc_apt_configure.handle("test", cfg, mycloud, LOG, None)
126+
127+ self.assertEqual(0, mock_writefile.call_count)
128+ self.assertEqual(1, mock_issnappy.call_count)
129+
130+ def test_apt_v3_source_list_centos(self):
131+ """test_apt_v3_source_list_centos - without custom sources or parms"""
132+ cfg = {}
133+ distro = 'rhel'
134+
135+ mock_writefile, _, _, _ = self._apt_source_list(distro, cfg)
136+
137+ self.assertEqual(0, mock_writefile.call_count)
138
139 def test_apt_v3_source_list_psm(self):
140 """test_apt_v3_source_list_psm - Test specifying prim+sec mirrors"""
141@@ -164,8 +207,17 @@ class TestAptSourceConfigSourceList(t_help.FilesystemMockingTestCase):
142 'uri': pm}],
143 'security': [{'arches': ["default"],
144 'uri': sm}]}
145+ distro = 'ubuntu'
146+ expected = EXPECTED_PRIMSEC_CONTENT
147+
148+ mock_writefile, mock_load_file, mock_isfile, _ = (
149+ self._apt_source_list(distro, cfg, cfg_on_empty=True))
150
151- self._apt_source_list(cfg, EXPECTED_PRIMSEC_CONTENT, 'ubuntu')
152+ template = '/etc/cloud/templates/sources.list.%s.tmpl' % distro
153+ mock_writefile.assert_called_once_with('/etc/apt/sources.list',
154+ expected, mode=0o644)
155+ mock_load_file.assert_called_with(template)
156+ mock_isfile.assert_any_call(template)
157
158 def test_apt_v3_srcl_custom(self):
159 """test_apt_v3_srcl_custom - Test rendering a custom source template"""
160diff --git a/tests/unittests/test_handler/test_handler_yum_add_repo.py b/tests/unittests/test_handler/test_handler_yum_add_repo.py
161index 4815bdb..c4396df 100644
162--- a/tests/unittests/test_handler/test_handler_yum_add_repo.py
163+++ b/tests/unittests/test_handler/test_handler_yum_add_repo.py
164@@ -72,7 +72,7 @@ class TestConfig(helpers.FilesystemMockingTestCase):
165 }
166 for section in expected:
167 self.assertTrue(parser.has_section(section),
168- "Contains section {}".format(section))
169+ "Contains section {0}".format(section))
170 for k, v in expected[section].items():
171 self.assertEqual(parser.get(section, k), v)
172
173@@ -109,7 +109,7 @@ class TestConfig(helpers.FilesystemMockingTestCase):
174 }
175 for section in expected:
176 self.assertTrue(parser.has_section(section),
177- "Contains section {}".format(section))
178+ "Contains section {0}".format(section))
179 for k, v in expected[section].items():
180 self.assertEqual(parser.get(section, k), v)
181
182diff --git a/tox.ini b/tox.ini
183index bf9046a..826f554 100644
184--- a/tox.ini
185+++ b/tox.ini
186@@ -70,7 +70,7 @@ deps =
187
188 [testenv:centos6]
189 basepython = python2.6
190-commands = nosetests {posargs:tests}
191+commands = nosetests {posargs:tests/unittests}
192 deps =
193 # requirements
194 argparse==1.2.1

Subscribers

People subscribed via source and target branches