1# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com> 2# 3# This file is part of Ansible 4# 5# Ansible is free software: you can redistribute it and/or modify 6# it under the terms of the GNU General Public License as published by 7# the Free Software Foundation, either version 3 of the License, or 8# (at your option) any later version. 9# 10# Ansible is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with Ansible. If not, see <http://www.gnu.org/licenses/>. 17 18# Make coding more python3-ish 19from __future__ import (absolute_import, division, print_function) 20__metaclass__ = type 21 22from units.compat import unittest 23from units.compat.mock import patch, MagicMock 24 25from ansible.errors import AnsibleError, AnsibleParserError 26from ansible.module_utils.common._collections_compat import Container 27from ansible.playbook.block import Block 28 29from units.mock.loader import DictDataLoader 30from units.mock.path import mock_unfrackpath_noop 31 32from ansible.playbook.role import Role 33from ansible.playbook.role.include import RoleInclude 34from ansible.playbook.role import hash_params 35 36 37class TestHashParams(unittest.TestCase): 38 def test(self): 39 params = {'foo': 'bar'} 40 res = hash_params(params) 41 self._assert_set(res) 42 self._assert_hashable(res) 43 44 def _assert_hashable(self, res): 45 a_dict = {} 46 try: 47 a_dict[res] = res 48 except TypeError as e: 49 self.fail('%s is not hashable: %s' % (res, e)) 50 51 def _assert_set(self, res): 52 self.assertIsInstance(res, frozenset) 53 54 def test_dict_tuple(self): 55 params = {'foo': (1, 'bar',)} 56 res = hash_params(params) 57 self._assert_set(res) 58 59 def test_tuple(self): 60 params = (1, None, 'foo') 61 res = hash_params(params) 62 self._assert_hashable(res) 63 64 def test_tuple_dict(self): 65 params = ({'foo': 'bar'}, 37) 66 res = hash_params(params) 67 self._assert_hashable(res) 68 69 def test_list(self): 70 params = ['foo', 'bar', 1, 37, None] 71 res = hash_params(params) 72 self._assert_set(res) 73 self._assert_hashable(res) 74 75 def test_dict_with_list_value(self): 76 params = {'foo': [1, 4, 'bar']} 77 res = hash_params(params) 78 self._assert_set(res) 79 self._assert_hashable(res) 80 81 def test_empty_set(self): 82 params = set([]) 83 res = hash_params(params) 84 self._assert_hashable(res) 85 self._assert_set(res) 86 87 def test_generator(self): 88 def my_generator(): 89 for i in ['a', 1, None, {}]: 90 yield i 91 92 params = my_generator() 93 res = hash_params(params) 94 self._assert_hashable(res) 95 96 def test_container_but_not_iterable(self): 97 # This is a Container that is not iterable, which is unlikely but... 98 class MyContainer(Container): 99 def __init__(self, some_thing): 100 self.data = [] 101 self.data.append(some_thing) 102 103 def __contains__(self, item): 104 return item in self.data 105 106 def __hash__(self): 107 return hash(self.data) 108 109 def __len__(self): 110 return len(self.data) 111 112 def __call__(self): 113 return False 114 115 foo = MyContainer('foo bar') 116 params = foo 117 118 self.assertRaises(TypeError, hash_params, params) 119 120 def test_param_dict_dupe_values(self): 121 params1 = {'foo': False} 122 params2 = {'bar': False} 123 124 res1 = hash_params(params1) 125 res2 = hash_params(params2) 126 127 hash1 = hash(res1) 128 hash2 = hash(res2) 129 self.assertNotEqual(res1, res2) 130 self.assertNotEqual(hash1, hash2) 131 132 def test_param_dupe(self): 133 params1 = { 134 # 'from_files': {}, 135 'tags': [], 136 u'testvalue': False, 137 u'testvalue2': True, 138 # 'when': [] 139 } 140 params2 = { 141 # 'from_files': {}, 142 'tags': [], 143 u'testvalue': True, 144 u'testvalue2': False, 145 # 'when': [] 146 } 147 res1 = hash_params(params1) 148 res2 = hash_params(params2) 149 150 self.assertNotEqual(hash(res1), hash(res2)) 151 self.assertNotEqual(res1, res2) 152 153 foo = {} 154 foo[res1] = 'params1' 155 foo[res2] = 'params2' 156 157 self.assertEqual(len(foo), 2) 158 159 del foo[res2] 160 self.assertEqual(len(foo), 1) 161 162 for key in foo: 163 self.assertTrue(key in foo) 164 self.assertIn(key, foo) 165 166 167class TestRole(unittest.TestCase): 168 169 @patch('ansible.playbook.role.definition.unfrackpath', mock_unfrackpath_noop) 170 def test_load_role_with_tasks(self): 171 172 fake_loader = DictDataLoader({ 173 "/usr/local/etc/ansible/roles/foo_tasks/tasks/main.yml": """ 174 - shell: echo 'hello world' 175 """, 176 }) 177 178 mock_play = MagicMock() 179 mock_play.ROLE_CACHE = {} 180 181 i = RoleInclude.load('foo_tasks', play=mock_play, loader=fake_loader) 182 r = Role.load(i, play=mock_play) 183 184 self.assertEqual(str(r), 'foo_tasks') 185 self.assertEqual(len(r._task_blocks), 1) 186 assert isinstance(r._task_blocks[0], Block) 187 188 @patch('ansible.playbook.role.definition.unfrackpath', mock_unfrackpath_noop) 189 def test_load_role_with_tasks_dir_vs_file(self): 190 191 fake_loader = DictDataLoader({ 192 "/usr/local/etc/ansible/roles/foo_tasks/tasks/custom_main/foo.yml": """ 193 - command: bar 194 """, 195 "/usr/local/etc/ansible/roles/foo_tasks/tasks/custom_main.yml": """ 196 - command: baz 197 """, 198 }) 199 200 mock_play = MagicMock() 201 mock_play.ROLE_CACHE = {} 202 203 i = RoleInclude.load('foo_tasks', play=mock_play, loader=fake_loader) 204 r = Role.load(i, play=mock_play, from_files=dict(tasks='custom_main')) 205 206 self.assertEqual(r._task_blocks[0]._ds[0]['command'], 'baz') 207 208 @patch('ansible.playbook.role.definition.unfrackpath', mock_unfrackpath_noop) 209 def test_load_role_with_handlers(self): 210 211 fake_loader = DictDataLoader({ 212 "/usr/local/etc/ansible/roles/foo_handlers/handlers/main.yml": """ 213 - name: test handler 214 shell: echo 'hello world' 215 """, 216 }) 217 218 mock_play = MagicMock() 219 mock_play.ROLE_CACHE = {} 220 221 i = RoleInclude.load('foo_handlers', play=mock_play, loader=fake_loader) 222 r = Role.load(i, play=mock_play) 223 224 self.assertEqual(len(r._handler_blocks), 1) 225 assert isinstance(r._handler_blocks[0], Block) 226 227 @patch('ansible.playbook.role.definition.unfrackpath', mock_unfrackpath_noop) 228 def test_load_role_with_vars(self): 229 230 fake_loader = DictDataLoader({ 231 "/usr/local/etc/ansible/roles/foo_vars/defaults/main.yml": """ 232 foo: bar 233 """, 234 "/usr/local/etc/ansible/roles/foo_vars/vars/main.yml": """ 235 foo: bam 236 """, 237 }) 238 239 mock_play = MagicMock() 240 mock_play.ROLE_CACHE = {} 241 242 i = RoleInclude.load('foo_vars', play=mock_play, loader=fake_loader) 243 r = Role.load(i, play=mock_play) 244 245 self.assertEqual(r._default_vars, dict(foo='bar')) 246 self.assertEqual(r._role_vars, dict(foo='bam')) 247 248 @patch('ansible.playbook.role.definition.unfrackpath', mock_unfrackpath_noop) 249 def test_load_role_with_vars_dirs(self): 250 251 fake_loader = DictDataLoader({ 252 "/usr/local/etc/ansible/roles/foo_vars/defaults/main/foo.yml": """ 253 foo: bar 254 """, 255 "/usr/local/etc/ansible/roles/foo_vars/vars/main/bar.yml": """ 256 foo: bam 257 """, 258 }) 259 260 mock_play = MagicMock() 261 mock_play.ROLE_CACHE = {} 262 263 i = RoleInclude.load('foo_vars', play=mock_play, loader=fake_loader) 264 r = Role.load(i, play=mock_play) 265 266 self.assertEqual(r._default_vars, dict(foo='bar')) 267 self.assertEqual(r._role_vars, dict(foo='bam')) 268 269 @patch('ansible.playbook.role.definition.unfrackpath', mock_unfrackpath_noop) 270 def test_load_role_with_vars_nested_dirs(self): 271 272 fake_loader = DictDataLoader({ 273 "/usr/local/etc/ansible/roles/foo_vars/defaults/main/foo/bar.yml": """ 274 foo: bar 275 """, 276 "/usr/local/etc/ansible/roles/foo_vars/vars/main/bar/foo.yml": """ 277 foo: bam 278 """, 279 }) 280 281 mock_play = MagicMock() 282 mock_play.ROLE_CACHE = {} 283 284 i = RoleInclude.load('foo_vars', play=mock_play, loader=fake_loader) 285 r = Role.load(i, play=mock_play) 286 287 self.assertEqual(r._default_vars, dict(foo='bar')) 288 self.assertEqual(r._role_vars, dict(foo='bam')) 289 290 @patch('ansible.playbook.role.definition.unfrackpath', mock_unfrackpath_noop) 291 def test_load_role_with_vars_nested_dirs_combined(self): 292 293 fake_loader = DictDataLoader({ 294 "/usr/local/etc/ansible/roles/foo_vars/defaults/main/foo/bar.yml": """ 295 foo: bar 296 a: 1 297 """, 298 "/usr/local/etc/ansible/roles/foo_vars/defaults/main/bar/foo.yml": """ 299 foo: bam 300 b: 2 301 """, 302 }) 303 304 mock_play = MagicMock() 305 mock_play.ROLE_CACHE = {} 306 307 i = RoleInclude.load('foo_vars', play=mock_play, loader=fake_loader) 308 r = Role.load(i, play=mock_play) 309 310 self.assertEqual(r._default_vars, dict(foo='bar', a=1, b=2)) 311 312 @patch('ansible.playbook.role.definition.unfrackpath', mock_unfrackpath_noop) 313 def test_load_role_with_vars_dir_vs_file(self): 314 315 fake_loader = DictDataLoader({ 316 "/usr/local/etc/ansible/roles/foo_vars/vars/main/foo.yml": """ 317 foo: bar 318 """, 319 "/usr/local/etc/ansible/roles/foo_vars/vars/main.yml": """ 320 foo: bam 321 """, 322 }) 323 324 mock_play = MagicMock() 325 mock_play.ROLE_CACHE = {} 326 327 i = RoleInclude.load('foo_vars', play=mock_play, loader=fake_loader) 328 r = Role.load(i, play=mock_play) 329 330 self.assertEqual(r._role_vars, dict(foo='bam')) 331 332 @patch('ansible.playbook.role.definition.unfrackpath', mock_unfrackpath_noop) 333 def test_load_role_with_metadata(self): 334 335 fake_loader = DictDataLoader({ 336 '/usr/local/etc/ansible/roles/foo_metadata/meta/main.yml': """ 337 allow_duplicates: true 338 dependencies: 339 - bar_metadata 340 galaxy_info: 341 a: 1 342 b: 2 343 c: 3 344 """, 345 '/usr/local/etc/ansible/roles/bar_metadata/meta/main.yml': """ 346 dependencies: 347 - baz_metadata 348 """, 349 '/usr/local/etc/ansible/roles/baz_metadata/meta/main.yml': """ 350 dependencies: 351 - bam_metadata 352 """, 353 '/usr/local/etc/ansible/roles/bam_metadata/meta/main.yml': """ 354 dependencies: [] 355 """, 356 '/usr/local/etc/ansible/roles/bad1_metadata/meta/main.yml': """ 357 1 358 """, 359 '/usr/local/etc/ansible/roles/bad2_metadata/meta/main.yml': """ 360 foo: bar 361 """, 362 '/usr/local/etc/ansible/roles/recursive1_metadata/meta/main.yml': """ 363 dependencies: ['recursive2_metadata'] 364 """, 365 '/usr/local/etc/ansible/roles/recursive2_metadata/meta/main.yml': """ 366 dependencies: ['recursive1_metadata'] 367 """, 368 }) 369 370 mock_play = MagicMock() 371 mock_play.collections = None 372 mock_play.ROLE_CACHE = {} 373 374 i = RoleInclude.load('foo_metadata', play=mock_play, loader=fake_loader) 375 r = Role.load(i, play=mock_play) 376 377 role_deps = r.get_direct_dependencies() 378 379 self.assertEqual(len(role_deps), 1) 380 self.assertEqual(type(role_deps[0]), Role) 381 self.assertEqual(len(role_deps[0].get_parents()), 1) 382 self.assertEqual(role_deps[0].get_parents()[0], r) 383 self.assertEqual(r._metadata.allow_duplicates, True) 384 self.assertEqual(r._metadata.galaxy_info, dict(a=1, b=2, c=3)) 385 386 all_deps = r.get_all_dependencies() 387 self.assertEqual(len(all_deps), 3) 388 self.assertEqual(all_deps[0].get_name(), 'bam_metadata') 389 self.assertEqual(all_deps[1].get_name(), 'baz_metadata') 390 self.assertEqual(all_deps[2].get_name(), 'bar_metadata') 391 392 i = RoleInclude.load('bad1_metadata', play=mock_play, loader=fake_loader) 393 self.assertRaises(AnsibleParserError, Role.load, i, play=mock_play) 394 395 i = RoleInclude.load('bad2_metadata', play=mock_play, loader=fake_loader) 396 self.assertRaises(AnsibleParserError, Role.load, i, play=mock_play) 397 398 # TODO: re-enable this test once Ansible has proper role dep cycle detection 399 # that doesn't rely on stack overflows being recoverable (as they aren't in Py3.7+) 400 # see https://github.com/ansible/ansible/issues/61527 401 # i = RoleInclude.load('recursive1_metadata', play=mock_play, loader=fake_loader) 402 # self.assertRaises(AnsibleError, Role.load, i, play=mock_play) 403 404 @patch('ansible.playbook.role.definition.unfrackpath', mock_unfrackpath_noop) 405 def test_load_role_complex(self): 406 407 # FIXME: add tests for the more complex uses of 408 # params and tags/when statements 409 410 fake_loader = DictDataLoader({ 411 "/usr/local/etc/ansible/roles/foo_complex/tasks/main.yml": """ 412 - shell: echo 'hello world' 413 """, 414 }) 415 416 mock_play = MagicMock() 417 mock_play.ROLE_CACHE = {} 418 419 i = RoleInclude.load(dict(role='foo_complex'), play=mock_play, loader=fake_loader) 420 r = Role.load(i, play=mock_play) 421 422 self.assertEqual(r.get_name(), "foo_complex") 423