From 1701904ca39575f49bb7e4a78de28d2f98a66fb9 Mon Sep 17 00:00:00 2001 From: Jiri Podivin Date: Mon, 18 Jan 2021 16:55:50 +0100 Subject: [PATCH] Tests verifying functionality ansible runtime infrastructure New tests verify callback order of precedence, white list handling and environment variable setting under various conditions. Closes-Bug: #1922726 Signed-off-by: Jiri Podivin Change-Id: I20b0c9a4289c7c8885e5b1981c8bea81c1deab47 --- validations_libs/tests/test_ansible.py | 618 ++++++++++++++++++++++++- 1 file changed, 615 insertions(+), 3 deletions(-) diff --git a/validations_libs/tests/test_ansible.py b/validations_libs/tests/test_ansible.py index 7d6269ec..38b32707 100644 --- a/validations_libs/tests/test_ansible.py +++ b/validations_libs/tests/test_ansible.py @@ -19,12 +19,14 @@ try: except ImportError: import mock from unittest import TestCase +import os from ansible_runner import Runner from validations_libs import constants from validations_libs.ansible import Ansible from validations_libs.tests import fakes + try: version = pkg_resources.get_distribution("ansible_runner").version backward_compat = (version < '1.4.0') @@ -363,10 +365,10 @@ class TestAnsible(TestCase): return_value="/foo/inventory.yaml") @mock.patch('ansible_runner.runner_config.RunnerConfig') @mock.patch('validations_libs.ansible.Ansible._ansible_env_var', - return_value={'ANSIBLE_STDOUT_CALLBACK': 'fake.py'}) + return_value={'ANSIBLE_STDOUT_CALLBACK': 'fake'}) @mock.patch('os.environ.copy', return_value={}) @mock.patch('os.path.abspath', return_value='/tmp/foo/localhost') - def test_run_specific_log_path(self, moch_path, mock_env, mock_env_var, + def test_run_specific_log_path(self, mock_path, mock_env, mock_env_var, mock_config, mock_dump_artifact, mock_run, mock_mkdirs, mock_exists, mock_open): _playbook, _rc, _status = self.run.run( @@ -390,7 +392,7 @@ class TestAnsible(TestCase): if not backward_compat: opt.update({ 'envvars': { - 'ANSIBLE_STDOUT_CALLBACK': 'fake.py', + 'ANSIBLE_STDOUT_CALLBACK': 'fake', 'ANSIBLE_CONFIG': '/tmp/foo/artifacts/ansible.cfg', 'VALIDATIONS_LOG_DIR': '/tmp/foo'}, 'project_dir': '/tmp', @@ -479,3 +481,613 @@ class TestAnsible(TestCase): self.assertEqual((_playbook, _rc, _status), ('existing.yaml', 0, 'successful')) mock_open.assert_called_with('/tmp/ansible.cfg', 'w') + + @mock.patch('six.moves.builtins.open') + @mock.patch('os.path.exists', return_value=True) + @mock.patch.object( + Runner, + 'run', + return_value=fakes.fake_ansible_runner_run_return(rc=0)) + @mock.patch( + 'ansible_runner.utils.dump_artifact', + autospec=True, + return_value="/foo/inventory.yaml") + @mock.patch('ansible_runner.runner_config.RunnerConfig') + @mock.patch( + 'validations_libs.ansible.Ansible._ansible_env_var', + return_value={'ANSIBLE_STDOUT_CALLBACK': 'fake'}) + @mock.patch('os.path.abspath', return_value='/tmp/foo/localhost') + @mock.patch('os.environ.copy', return_value={}) + def test_run_no_log_path(self, mock_env, mock_path, + mock_env_var, mock_config, + mock_dump_artifact, mock_run, + mock_exists, mock_open): + """ + Tests if leaving default (None) log_path appropriately sets + 'ANSIBLE_CONFIG' and 'fact_cache' envvars, + using constants.constants.VALIDATION_ANSIBLE_ARTIFACT_PATH. + Bulk of the mocks are only for purposes of convenience. + + Assertions: + Presence of key: value pairs. + """ + _playbook, _rc, _status = self.run.run( + playbook='existing.yaml', + inventory='localhost,', + workdir='/tmp') + + opt = { + 'artifact_dir': '/tmp', + 'extravars': {}, + 'ident': '', + 'inventory': '/tmp/foo/localhost', + 'playbook': 'existing.yaml', + 'private_data_dir': '/tmp', + 'quiet': False, + 'rotate_artifacts': 256, + 'verbosity': 0} + + if not backward_compat: + opt.update({ + 'envvars': { + 'ANSIBLE_STDOUT_CALLBACK': 'fake', + 'ANSIBLE_CONFIG': os.path.join( + constants.VALIDATION_ANSIBLE_ARTIFACT_PATH, + 'ansible.cfg')}, + 'project_dir': '/tmp', + 'fact_cache': constants.VALIDATION_ANSIBLE_ARTIFACT_PATH, + 'fact_cache_type': 'jsonfile'}) + + mock_config.assert_called_once_with(**opt) + + @mock.patch('six.moves.builtins.open') + @mock.patch('os.path.exists', return_value=True) + @mock.patch.object( + Runner, + 'run', + return_value=fakes.fake_ansible_runner_run_return(rc=0)) + @mock.patch( + 'ansible_runner.utils.dump_artifact', + autospec=True, + return_value="/foo/inventory.yaml") + @mock.patch('ansible_runner.runner_config.RunnerConfig') + @mock.patch( + 'validations_libs.ansible.Ansible._ansible_env_var', + return_value={'ANSIBLE_STDOUT_CALLBACK': 'fake'}) + @mock.patch('os.path.abspath', return_value='/tmp/foo/localhost') + @mock.patch('os.environ.copy', return_value={}) + def test_run_tags(self, mock_env, mock_path, + mock_env_var, mock_config, + mock_dump_artifact, mock_run, + mock_exists, mock_open): + """ + Tests if specifying tags appropriately sets + 'tags' envvar, passed as dict entry to RunnerConfig. + Bulk of the mocks are only for purposes of convenience. + + Assertions: + Presence of key: value pairs. + """ + tags = ','.join(['master', 'train', 'fake']) + + _playbook, _rc, _status = self.run.run( + playbook='existing.yaml', + inventory='localhost,', + workdir='/tmp', + log_path='/tmp/foo', + tags=tags) + + opt = { + 'artifact_dir': '/tmp', + 'extravars': {}, + 'ident': '', + 'inventory': '/tmp/foo/localhost', + 'playbook': 'existing.yaml', + 'private_data_dir': '/tmp', + 'quiet': False, + 'rotate_artifacts': 256, + 'verbosity': 0, + 'tags': tags} + + if not backward_compat: + opt.update({ + 'envvars': { + 'ANSIBLE_STDOUT_CALLBACK': 'fake', + 'ANSIBLE_CONFIG': '/tmp/foo/artifacts/ansible.cfg', + 'VALIDATIONS_LOG_DIR': '/tmp/foo'}, + 'project_dir': '/tmp', + 'fact_cache': '/tmp/foo/artifacts/', + 'fact_cache_type': 'jsonfile'}) + + mock_config.assert_called_once_with(**opt) + + @mock.patch('six.moves.builtins.open') + @mock.patch('os.path.exists', return_value=True) + @mock.patch.object( + Runner, + 'run', + return_value=fakes.fake_ansible_runner_run_return(rc=0)) + @mock.patch('ansible_runner.runner_config.RunnerConfig') + @mock.patch( + 'validations_libs.ansible.Ansible._encode_envvars', + return_value={ + 'ANSIBLE_STDOUT_CALLBACK': 'fake', + 'ANSIBLE_CALLBACK_WHITELIST': 'fake,validation_json,profile_tasks', + 'ANSIBLE_CONFIG': os.path.join( + constants.VALIDATION_ANSIBLE_ARTIFACT_PATH, + 'ansible.cfg')}) + @mock.patch( + 'os.environ.copy', + return_value={'ANSIBLE_STDOUT_CALLBACK': 'fake'}) + @mock.patch('os.path.abspath', return_value='/tmp/foo/localhost') + def test_run_ansible_playbook_dir(self, mock_path, mock_env, + mock_encode_envvars, + mock_config, mock_run, + mock_exists, mock_open): + """ + Tests if leaving default (None) log_path and setting playbook_dir + appropriately sets 'project_dir' value in r_opts dict. + Bulk of the mocks are only for purposes of convenience. + + Assertions: + Presence of key: value pairs. + """ + _playbook, _rc, _status = self.run.run( + playbook='existing.yaml', + inventory='localhost,', + workdir='/tmp', + playbook_dir='/tmp/fake_playbooks') + + opt = { + 'artifact_dir': '/tmp', + 'extravars': {}, + 'ident': '', + 'inventory': '/tmp/foo/localhost', + 'playbook': 'existing.yaml', + 'private_data_dir': '/tmp', + 'quiet': False, + 'rotate_artifacts': 256, + 'verbosity': 0} + + if not backward_compat: + opt.update({ + 'envvars': { + 'ANSIBLE_STDOUT_CALLBACK': 'fake', + 'ANSIBLE_CONFIG': os.path.join( + constants.VALIDATION_ANSIBLE_ARTIFACT_PATH, + 'ansible.cfg'), + 'ANSIBLE_CALLBACK_WHITELIST': 'fake,validation_json,profile_tasks' + }, + 'project_dir': '/tmp/fake_playbooks', + 'fact_cache': constants.VALIDATION_ANSIBLE_ARTIFACT_PATH, + 'fact_cache_type': 'jsonfile'}) + + mock_config.assert_called_once_with(**opt) + + @mock.patch('six.moves.builtins.open') + @mock.patch('os.path.exists', return_value=True) + @mock.patch.object( + Runner, + 'run', + return_value=fakes.fake_ansible_runner_run_return(rc=0)) + @mock.patch('ansible_runner.runner_config.RunnerConfig') + @mock.patch( + 'validations_libs.ansible.Ansible._ansible_env_var', + return_value={ + 'ANSIBLE_STDOUT_CALLBACK': 'fake', + 'ANSIBLE_CALLBACK_WHITELIST': 'log_plays,mail,fake,validation_json,profile_tasks' + }) + @mock.patch('os.environ.copy', return_value={}) + def test_run_callback_whitelist_extend(self, mock_env, + mock_env_var, mock_config, + mock_run, mock_exists, + mock_open): + """Tests if Ansible._callbacks method appropriately constructs callback_whitelist, + when provided explicit whitelist and output_callback. + Bulk of the mocks are only for purposes of convenience. + + Assertions: + Presence of key: value pairs. + """ + _playbook, _rc, _status = self.run.run( + ssh_user='root', + playbook='existing.yaml', + inventory='localhost,', + workdir='/tmp', + log_path='/tmp/foo', + output_callback='fake', + callback_whitelist='log_plays,mail') + + args = { + 'output_callback': 'fake', + 'ssh_user': 'root', + 'workdir': '/tmp', + 'connection': 'smart', + 'gathering_policy': 'smart', + 'module_path': None, + 'key': None, + 'extra_env_variables': None, + 'ansible_timeout': 30, + 'callback_whitelist': 'log_plays,mail,fake,profile_tasks,vf_validation_json', + 'base_dir': '/usr/share/ansible', + 'python_interpreter': None} + + #Specific form of Ansible.env_var neccessiates convoluted arg unpacking. + mock_env_var.assert_called_once_with(*args.values(), validation_cfg_file=None) + + @mock.patch('six.moves.builtins.open') + @mock.patch('os.path.exists', return_value=True) + @mock.patch.object( + Runner, + 'run', + return_value=fakes.fake_ansible_runner_run_return(rc=0)) + @mock.patch('ansible_runner.runner_config.RunnerConfig') + @mock.patch( + 'validations_libs.ansible.Ansible._ansible_env_var', + return_value={ + 'ANSIBLE_STDOUT_CALLBACK': 'fake', + 'ANSIBLE_CALLBACK_WHITELIST': 'fake,validation_json,profile_tasks' + }) + @mock.patch('os.environ.copy', return_value={}) + def test_run_callback_whitelist_none(self, mock_env, + mock_env_var, mock_config, + mock_run, mock_exists, + mock_open): + """Tests if Ansible._callbacks method appropriately constructs callback_whitelist, + when provided default (None) whitelist and specific output_callback. + Bulk of the mocks are only for purposes of convenience. + + Assertions: + Presence of key: value pairs. + """ + _playbook, _rc, _status = self.run.run( + ssh_user='root', + playbook='existing.yaml', + inventory='localhost,', + workdir='/tmp', + log_path='/tmp/foo', + output_callback='fake') + + args = { + 'output_callback': 'fake', + 'ssh_user': 'root', + 'workdir': '/tmp', + 'connection': 'smart', + 'gathering_policy': 'smart', + 'module_path': None, + 'key': None, + 'extra_env_variables': None, + 'ansible_timeout': 30, + 'callback_whitelist': 'fake,profile_tasks,vf_validation_json', + 'base_dir': '/usr/share/ansible', + 'python_interpreter': None} + + #Specific form of Ansible.env_var neccessiates convoluted arg unpacking. + mock_env_var.assert_called_once_with(*args.values(), validation_cfg_file=None) + + @mock.patch('six.moves.builtins.open') + @mock.patch('os.path.exists', return_value=True) + @mock.patch.object( + Runner, + 'run', + return_value=fakes.fake_ansible_runner_run_return(rc=0)) + @mock.patch('ansible_runner.runner_config.RunnerConfig') + @mock.patch( + 'validations_libs.ansible.Ansible._ansible_env_var', + return_value={ + 'ANSIBLE_STDOUT_CALLBACK': 'different_fake', + 'ANSIBLE_CALLBACK_WHITELIST': 'different_fake,validation_json,profile_tasks' + }) + @mock.patch( + 'os.environ.copy', + return_value={'ANSIBLE_STDOUT_CALLBACK': 'different_fake'}) + def test_run_callback_precedence(self, mock_env, + mock_env_var, mock_config, + mock_run, mock_exists, mock_open): + """Tests if Ansible._callbacks method reaches for output_callback + if and only if env dict doesn't contain 'ANSIBLE_STDOUT_CALLBACK' key. + Bulk of the mocks are only for purposes of convenience. + + Assertions: + Presence of key: value pairs. + """ + _playbook, _rc, _status = self.run.run( + ssh_user='root', + playbook='existing.yaml', + inventory='localhost,', + workdir='/tmp', + log_path='/tmp/foo', + output_callback='fake') + + args = { + 'output_callback': 'different_fake', + 'ssh_user': 'root', + 'workdir': '/tmp', + 'connection': 'smart', + 'gathering_policy': 'smart', + 'module_path': None, + 'key': None, + 'extra_env_variables': None, + 'ansible_timeout': 30, + 'callback_whitelist': 'different_fake,profile_tasks,vf_validation_json', + 'base_dir': '/usr/share/ansible', + 'python_interpreter': None} + + #Specific form of Ansible.env_var neccessiates convoluted arg unpacking. + mock_env_var.assert_called_once_with(*args.values(), validation_cfg_file=None) + + @mock.patch('six.moves.builtins.open') + @mock.patch('os.path.exists', return_value=True) + @mock.patch.object( + Runner, + 'run', + return_value=fakes.fake_ansible_runner_run_return(rc=0)) + @mock.patch('ansible_runner.runner_config.RunnerConfig') + @mock.patch( + 'validations_libs.ansible.Ansible._ansible_env_var', + return_value={ + 'ANSIBLE_STDOUT_CALLBACK': 'fake', + 'ANSIBLE_CALLBACK_WHITELIST': 'fake,validation_json,profile_tasks' + }) + @mock.patch( + 'os.environ.copy', + return_value={'ANSIBLE_STDOUT_CALLBACK': 'fake'}) + def test_run_ansible_artifact_path_set(self, mock_env, + mock_env_var, mock_config, + mock_run, mock_exists, mock_open): + """Tests if specified 'ansible_artifact_path' is passed in a valid + and unchanged form to RunnerConfig as value of 'fact_cache' param. + Additional assertion on number of calls is placed, + to ensure that RunnerConfig is called only once. + Otherwise followup assertions could fail. + + Assertions: + Validity of specified path in filesystem: + os.lstat raises FileNotFoundError only if specified path is valid, + but does not exist in current filesystem. + + Passing of specified value (ansible_artifact_path) to RunnerConfig. + """ + _playbook, _rc, _status = self.run.run( + playbook='existing.yaml', + inventory='localhost,', + workdir='/tmp', + log_path='/tmp/foo', + output_callback='fake', + ansible_artifact_path='/tmp/artifact/path') + + mock_config.assert_called_once() + + """Is the path even valid in our filesystem? Index 1 stands for kwargs in py<=36. + os.lstat raises FileNotFoundError only if specified path is valid, + but does not exist in current filesystem. + """ + self.assertRaises(FileNotFoundError, os.lstat, mock_config.call_args[1]['fact_cache']) + + self.assertTrue('/tmp/artifact/path' in mock_config.call_args[1]['fact_cache']) + + @mock.patch('six.moves.builtins.open') + @mock.patch('os.path.exists', return_value=True) + @mock.patch.object( + Runner, + 'run', + return_value=fakes.fake_ansible_runner_run_return(rc=0)) + @mock.patch('ansible_runner.runner_config.RunnerConfig') + @mock.patch( + 'validations_libs.ansible.Ansible._ansible_env_var', + return_value={ + 'ANSIBLE_STDOUT_CALLBACK': 'fake', + 'ANSIBLE_CALLBACK_WHITELIST': 'fake,validation_json,profile_tasks' + }) + @mock.patch( + 'os.environ.copy', + return_value={'ANSIBLE_STDOUT_CALLBACK': 'fake'}) + def test_run_ansible_artifact_path_from_log_path(self, mock_env, + mock_env_var, mock_config, + mock_run, mock_exists, mock_open): + """Tests if specified 'log_path' is passed in a valid + and unchanged form to RunnerConfig as value of 'fact_cache' param, + in absence of specified 'ansible_artifact_path'. + Additional assertion on number of calls is placed, + to ensure that RunnerConfig is called only once. + Otherwise followup assertions could fail. + + Assertions: + Validity of specified path in filesystem.: + os.lstat raises FileNotFoundError only if specified path is valid, + but does not exist in current filesystem. + + Passing of specified value (log_path) to RunnerConfig. + """ + _playbook, _rc, _status = self.run.run( + playbook='existing.yaml', + inventory='localhost,', + workdir='/tmp', + log_path='/tmp/foo', + output_callback='fake') + + mock_config.assert_called_once() + """Is the path even valid in our filesystem? Index 1 stands for kwargs in py<=36. + os.lstat raises FileNotFoundError only if specified path is valid, + but does not exist in current filesystem. + """ + self.assertRaises(FileNotFoundError, os.lstat, mock_config.call_args[1]['fact_cache']) + + self.assertTrue('/tmp/foo' in mock_config.call_args[1]['fact_cache']) + + @mock.patch.object( + constants, + 'VALIDATION_ANSIBLE_ARTIFACT_PATH', + new='foo/bar') + @mock.patch('six.moves.builtins.open') + @mock.patch('os.path.exists', return_value=True) + @mock.patch.object( + Runner, + 'run', + return_value=fakes.fake_ansible_runner_run_return(rc=0)) + @mock.patch('ansible_runner.runner_config.RunnerConfig') + @mock.patch( + 'validations_libs.ansible.Ansible._ansible_env_var', + return_value={ + 'ANSIBLE_STDOUT_CALLBACK': 'fake', + 'ANSIBLE_CALLBACK_WHITELIST': 'fake,validation_json,profile_tasks', + }) + @mock.patch( + 'os.environ.copy', + return_value={'ANSIBLE_STDOUT_CALLBACK': 'fake'}) + def test_run_ansible_artifact_path_from_constants(self, mock_env, + mock_env_var, mock_config, + mock_run, mock_exists, + mock_open): + """Tests if 'constants.constants.VALIDATION_ANSIBLE_ARTIFACT_PATH' passed in a valid + and unchanged form to RunnerConfig as value of 'fact_cache' param, + in absence of specified 'ansible_artifact_path' or 'log_path'. + Additional assertion on number of calls is placed, + to ensure that RunnerConfig is called only once. + Otherwise followup assertions could fail. + + Assertions: + Validity of specified path in filesystem.: + os.lstat raises FileNotFoundError only if specified path is valid, + but does not exist in current filesystem. + + Passing of specified value (log_path) to RunnerConfig. + """ + _playbook, _rc, _status = self.run.run( + playbook='existing.yaml', + inventory='localhost,', + workdir='/tmp') + + mock_config.assert_called_once() + """Is the path even valid in our filesystem? Index 1 stands for kwargs in py<=36. + os.lstat raises FileNotFoundError only if specified path is valid, + but does not exist in current filesystem. + """ + self.assertRaises(FileNotFoundError, os.lstat, mock_config.call_args[1]['fact_cache']) + + self.assertTrue(constants.VALIDATION_ANSIBLE_ARTIFACT_PATH in mock_config.call_args[1]['fact_cache']) + + @mock.patch('six.moves.builtins.open') + @mock.patch('os.path.exists', return_value=True) + @mock.patch.object( + Runner, + 'run', + return_value=fakes.fake_ansible_runner_run_return(rc=0)) + @mock.patch('ansible_runner.runner_config.RunnerConfig') + @mock.patch( + 'validations_libs.ansible.Ansible._encode_envvars', + return_value={ + 'ANSIBLE_STDOUT_CALLBACK': 'fake', + 'ANSIBLE_CALLBACK_WHITELIST': 'fake,validation_json,profile_tasks', + 'ANSIBLE_CONFIG': os.path.join( + constants.VALIDATION_ANSIBLE_ARTIFACT_PATH, + 'ansible.cfg')}) + @mock.patch( + 'os.environ.copy', + return_value={'ANSIBLE_STDOUT_CALLBACK': 'fake'}) + def test_run_ansible_envvars(self, mock_env, + mock_encode_envvars, + mock_config, mock_run, + mock_exists, mock_open): + """Tests if Ansible._ansible_env_var method, + and following conditionals, correctly assemble the env dict. + + Assertions: + Dictinary passed to Ansible._encode_envvars contains key: value + pairs representing proper superset of key: value pairs required. + """ + _playbook, _rc, _status = self.run.run( + playbook='existing.yaml', + inventory='localhost,', + workdir='/tmp') + + env = { + 'ANSIBLE_STDOUT_CALLBACK': 'fake', + 'ANSIBLE_DISPLAY_FAILED_STDERR': True, + 'ANSIBLE_FORKS': 36, + 'ANSIBLE_TIMEOUT': 30, + 'ANSIBLE_GATHER_TIMEOUT': 45, + 'ANSIBLE_SSH_RETRIES': 3, + 'ANSIBLE_PIPELINING': True, + 'ANSIBLE_CALLBACK_WHITELIST': 'fake,profile_tasks,vf_validation_json', + 'ANSIBLE_RETRY_FILES_ENABLED': False, + 'ANSIBLE_HOST_KEY_CHECKING': False, + 'ANSIBLE_TRANSPORT': 'smart', + 'ANSIBLE_CACHE_PLUGIN_TIMEOUT': 7200, + 'ANSIBLE_GATHERING': 'smart', + 'ANSIBLE_CONFIG': os.path.join( + constants.VALIDATION_ANSIBLE_ARTIFACT_PATH, + 'ansible.cfg')} + + #Test will work properly only if the method was called once. + mock_encode_envvars.assert_called_once() + + """True if, and only if, every item (key:value pair) in the env dict + is also present in the kwargs dict. Index 1 stands for kwargs in py<=36 + This test does not rely on order of items. + """ + self.assertGreaterEqual( + mock_encode_envvars.call_args[1]['env'].items(), + env.items()) + + @mock.patch('six.moves.builtins.open') + @mock.patch('os.path.exists', return_value=True) + @mock.patch.object( + Runner, + 'run', + return_value=fakes.fake_ansible_runner_run_return(rc=0)) + @mock.patch('ansible_runner.runner_config.RunnerConfig') + @mock.patch( + 'validations_libs.ansible.Ansible._encode_envvars', + return_value={ + 'ANSIBLE_STDOUT_CALLBACK': 'fake', + 'ANSIBLE_CALLBACK_WHITELIST': 'fake,validation_json,profile_tasks', + 'ANSIBLE_CONFIG': '/tmp/foo/artifacts/ansible.cfg'}) + @mock.patch( + 'os.environ.copy', + return_value={'ANSIBLE_STDOUT_CALLBACK': 'fake'}) + def test_run_ansible_envvars_logdir(self, mock_env, + mock_encode_envvars, + mock_config, mock_run, + mock_exists, mock_open): + """Tests if Ansible._ansible_env_var method, + and following conditionals, correctly assemble the env dict. + While using the specified `log_path` value in appropriate places. + + Assertions: + Dictinary passed to Ansible._encode_envvars contains key: value + pairs representing proper superset of key: value pairs required. + """ + _playbook, _rc, _status = self.run.run( + playbook='existing.yaml', + inventory='localhost,', + workdir='/tmp', + log_path='/tmp/foo') + + env = { + 'ANSIBLE_STDOUT_CALLBACK': 'fake', + 'ANSIBLE_DISPLAY_FAILED_STDERR': True, + 'ANSIBLE_FORKS': 36, + 'ANSIBLE_TIMEOUT': 30, + 'ANSIBLE_GATHER_TIMEOUT': 45, + 'ANSIBLE_SSH_RETRIES': 3, + 'ANSIBLE_PIPELINING': True, + 'ANSIBLE_CALLBACK_WHITELIST': 'fake,profile_tasks,vf_validation_json', + 'ANSIBLE_RETRY_FILES_ENABLED': False, + 'ANSIBLE_HOST_KEY_CHECKING': False, + 'ANSIBLE_TRANSPORT': 'smart', + 'ANSIBLE_CACHE_PLUGIN_TIMEOUT': 7200, + 'ANSIBLE_GATHERING': 'smart', + 'ANSIBLE_CONFIG': '/tmp/foo/artifacts/ansible.cfg', + 'VALIDATIONS_LOG_DIR': '/tmp/foo'} + + #Test will work properly only if the method was called once. + mock_encode_envvars.assert_called_once() + + """True if, and only if, every item (key:value pair) in the env dict + is also present in the kwargs dict. Index 1 stands for kwargs in py<=36 + This test does not rely on order of items. + """ + self.assertGreaterEqual( + mock_encode_envvars.call_args[1]['env'].items(), + env.items())