/
githubmirror
/
salt
Обзор
Документация
Войти
/
githubmirror
/
salt
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
tests/unit/modules/test_zypperpkg.py
2 044 строки
80 KB
Jeff Neel
Add zypperpkg module back to core
25 май 2026, 00:54
25 май 2026, 00:54
60afb87
Код
Авторство
О чём код?
""" :codeauthor: Bo Maryniuk <bo@suse.de> """ import configparser import errno import io import os from xml.dom import minidom import salt.modules.pkg_resource as pkg_resource import salt.modules.zypperpkg as zypper import salt.utils.files import salt.utils.pkg from salt.exceptions import CommandExecutionError from tests.support.mixins import LoaderModuleMockMixin from tests.support.mock import MagicMock, Mock, call, mock_open, patch from tests.support.unit import TestCase class ZyppCallMock: def __init__(self, return_value=None): self.__return_value = return_value def __getattr__(self, item): return self def __call__(self, *args, **kwargs): # If the call is for a configuration modifier, we return self if any(i in kwargs for i in ("no_repo_failure", "systemd_scope", "root")): return self return MagicMock(return_value=self.__return_value)() def get_test_data(filename): """ Return static test data """ with salt.utils.files.fopen( os.path.join( os.path.join(os.path.dirname(os.path.abspath(__file__)), "zypp"), filename ) ) as rfh: return rfh.read() class ZypperTestCase(TestCase, LoaderModuleMockMixin): """ Test cases for salt.modules.zypper """ def setup_loader_modules(self): return {zypper: {"rpm": None}, pkg_resource: {}} def setUp(self): self.new_repo_config = dict( name="mock-repo-name", url="http://repo.url/some/path" ) side_effect = [ Mock(**{"sections.return_value": []}), Mock(**{"sections.return_value": [self.new_repo_config["name"]]}), ] self.zypper_patcher_config = { "_get_configured_repos": Mock(side_effect=side_effect), "__zypper__": Mock(), "_get_repo_info": Mock( return_value={ "keeppackages": False, "autorefresh": True, "enabled": False, "baseurl": self.new_repo_config["url"], "alias": self.new_repo_config["name"], "name": self.new_repo_config["name"], "priority": 1, "type": "rpm-md", } ), "del_repo": Mock(), "mod_repo": Mock(wraps=zypper.mod_repo), } def tearDown(self): del self.new_repo_config del self.zypper_patcher_config def test_list_upgrades(self): """ List package upgrades :return: """ ref_out = { "stdout": get_test_data("zypper-updates.xml"), "stderr": None, "retcode": 0, } with patch.dict( zypper.__salt__, {"cmd.run_all": MagicMock(return_value=ref_out)} ), patch.object(zypper.__zypper__, "_is_rpm_lock", return_value=False): upgrades = zypper.list_upgrades(refresh=False) self.assertEqual(len(upgrades), 3) for pkg, version in { "SUSEConnect": "0.2.33-7.1", "bind-utils": "9.9.6P1-35.1", "bind-libs": "9.9.6P1-35.1", }.items(): self.assertIn(pkg, upgrades) self.assertEqual(upgrades[pkg], version) @patch( "salt.utils.environment.get_module_environment", MagicMock(return_value={"SALT_RUNNING": "1"}), ) def test_zypper_caller(self): """ Test Zypper caller. :return: """ class RunSniffer: def __init__(self, stdout=None, stderr=None, retcode=None): self.calls = list() self._stdout = stdout or "" self._stderr = stderr or "" self._retcode = retcode or 0 def __call__(self, *args, **kwargs): self.calls.append({"args": args, "kwargs": kwargs}) return { "stdout": self._stdout, "stderr": self._stderr, "retcode": self._retcode, } stdout_xml_snippet = '<?xml version="1.0"?><test foo="bar"/>' sniffer = RunSniffer(stdout=stdout_xml_snippet) with patch.dict("salt.modules.zypperpkg.__salt__", {"cmd.run_all": sniffer}): self.assertEqual(zypper.__zypper__.call("foo"), stdout_xml_snippet) self.assertEqual(len(sniffer.calls), 1) zypper.__zypper__.call("--no-refresh", "bar") self.assertEqual(len(sniffer.calls), 2) self.assertEqual( sniffer.calls[0]["args"][0], ["zypper", "--non-interactive", "--no-refresh", "foo"], ) self.assertEqual( sniffer.calls[1]["args"][0], ["zypper", "--non-interactive", "--no-refresh", "bar"], ) dom = zypper.__zypper__.xml.call("xml-test") self.assertEqual( sniffer.calls[2]["args"][0], ["zypper", "--non-interactive", "--xmlout", "--no-refresh", "xml-test"], ) self.assertEqual( dom.getElementsByTagName("test")[0].getAttribute("foo"), "bar" ) zypper.__zypper__.refreshable.call("refresh-test") self.assertEqual( sniffer.calls[3]["args"][0], ["zypper", "--non-interactive", "refresh-test"], ) zypper.__zypper__.nolock.call("no-locking-test") self.assertEqual( sniffer.calls[4] .get("kwargs", {}) .get("env", {}) .get("ZYPP_READONLY_HACK"), "1", ) self.assertEqual( sniffer.calls[4].get("kwargs", {}).get("env", {}).get("SALT_RUNNING"), "1", ) zypper.__zypper__.call("locking-test") self.assertEqual( sniffer.calls[5] .get("kwargs", {}) .get("env", {}) .get("ZYPP_READONLY_HACK"), None, ) self.assertEqual( sniffer.calls[5].get("kwargs", {}).get("env", {}).get("SALT_RUNNING"), "1", ) # Test exceptions stdout_xml_snippet = ( '<?xml version="1.0"?><stream><message' ' type="error">Booya!</message></stream>' ) sniffer = RunSniffer(stdout=stdout_xml_snippet, retcode=1) with patch.dict( "salt.modules.zypperpkg.__salt__", {"cmd.run_all": sniffer} ), patch.object(zypper.__zypper__, "_is_rpm_lock", return_value=False): with self.assertRaisesRegex( CommandExecutionError, "^Zypper command failure: Booya!$" ): zypper.__zypper__.xml.call("crashme") output_to_user_stdout = "Output to user to stdout" output_to_user_stderr = "Output to user to stderr" sniffer = RunSniffer( stdout=output_to_user_stdout, stderr=output_to_user_stderr, retcode=1 ) with patch.dict( "salt.modules.zypperpkg.__salt__", {"cmd.run_all": sniffer} ), patch.object(zypper.__zypper__, "_is_rpm_lock", return_value=False): with self.assertRaisesRegex( CommandExecutionError, "^Zypper command failure: {}$".format( output_to_user_stderr + output_to_user_stdout ), ): zypper.__zypper__.call("crashme again") sniffer = RunSniffer(retcode=1) with patch.dict( "salt.modules.zypperpkg.__salt__", {"cmd.run_all": sniffer} ), patch.object(zypper.__zypper__, "_is_rpm_lock", return_value=False): zypper.__zypper__.noraise.call("stay quiet") self.assertEqual(zypper.__zypper__.error_msg, "Check Zypper's logs.") def test_list_upgrades_error_handling(self): """ Test error handling in the list package upgrades. :return: """ # Test handled errors ref_out = { "stdout": """<?xml version='1.0'?> <stream> <message type="info">Refreshing service 'container-suseconnect'.</message> <message type="error">Some handled zypper internal error</message> <message type="error">Another zypper internal error</message> </stream> """, "stderr": "", "retcode": 1, } with patch.dict( "salt.modules.zypperpkg.__salt__", {"cmd.run_all": MagicMock(return_value=ref_out)}, ), patch.object(zypper.__zypper__, "_is_rpm_lock", return_value=False): with self.assertRaisesRegex( CommandExecutionError, "^Zypper command failure: Some handled zypper internal error{}Another" " zypper internal error$".format(os.linesep), ): zypper.list_upgrades(refresh=False) # Test unhandled error ref_out = {"retcode": 1, "stdout": "", "stderr": ""} with patch.dict( "salt.modules.zypperpkg.__salt__", {"cmd.run_all": MagicMock(return_value=ref_out)}, ), patch.object(zypper.__zypper__, "_is_rpm_lock", return_value=False): with self.assertRaisesRegex( CommandExecutionError, "^Zypper command failure: Check Zypper's logs.$" ): zypper.list_upgrades(refresh=False) def test_list_products(self): """ List products test. """ for filename, test_data in { "zypper-products-sle12sp1.xml": { "name": [ "SLES", "SLES", "SUSE-Manager-Proxy", "SUSE-Manager-Server", "sle-manager-tools-beta", "sle-manager-tools-beta-broken-eol", "sle-manager-tools-beta-no-eol", ], "vendor": "SUSE LLC <https://www.suse.com/>", "release": ["0", "0", "0", "0", "0", "0", "0"], "productline": [None, None, None, None, None, None, "sles"], "eol_t": [ None, 0, 1509408000, 1522454400, 1522454400, 1730332800, 1730332800, ], "isbase": [False, False, False, False, False, False, True], "installed": [False, False, False, False, False, False, True], "registerrelease": [None, None, None, None, None, None, "123"], }, "zypper-products-sle11sp3.xml": { "name": [ "SUSE-Manager-Server", "SUSE-Manager-Server", "SUSE-Manager-Server-Broken-EOL", "SUSE_SLES", "SUSE_SLES", "SUSE_SLES", "SUSE_SLES-SP4-migration", ], "vendor": "SUSE LINUX Products GmbH, Nuernberg, Germany", "release": ["1.138", "1.2", "1.2", "1.2", "1.201", "1.201", "1.4"], "productline": [None, None, None, None, None, "manager", "manager"], "eol_t": [None, 0, 0, 0, 0, 0, 0], "isbase": [False, False, False, False, False, True, True], "installed": [False, False, False, False, False, True, True], "registerrelease": [None, None, None, None, None, None, "42"], }, }.items(): ref_out = {"retcode": 0, "stdout": get_test_data(filename)} cmd_run_all = MagicMock(return_value=ref_out) mock_call = call( [ "zypper", "--non-interactive", "--xmlout", "--no-refresh", "--disable-repositories", "products", "-i", ], env={"ZYPP_READONLY_HACK": "1"}, output_loglevel="trace", python_shell=False, ) with patch.dict(zypper.__salt__, {"cmd.run_all": cmd_run_all}): products = zypper.list_products() self.assertEqual(len(products), 7) self.assertIn( test_data["vendor"], [product["vendor"] for product in products] ) for kwd in [ "name", "isbase", "installed", "release", "productline", "eol_t", "registerrelease", ]: self.assertCountEqual( test_data[kwd], [prod.get(kwd) for prod in products] ) cmd_run_all.assert_has_calls([mock_call]) def test_refresh_db(self): """ Test if refresh DB handled correctly """ ref_out = [ "Repository 'openSUSE-Leap-42.1-LATEST' is up to date.", "Repository 'openSUSE-Leap-42.1-Update' is up to date.", "Retrieving repository 'openSUSE-Leap-42.1-Update-Non-Oss' metadata", "Forcing building of repository cache", "Building repository 'openSUSE-Leap-42.1-Update-Non-Oss' cache" " ..........[done]", "Building repository 'salt-dev' cache", "All repositories have been refreshed.", ] run_out = {"stderr": "", "stdout": "\n".join(ref_out), "retcode": 0} zypper_mock = MagicMock(return_value=run_out) call_kwargs = { "output_loglevel": "trace", "python_shell": False, "env": {}, "success_retcodes": [0, 6], } with patch.dict(zypper.__salt__, {"cmd.run_all": zypper_mock}): with patch.object(salt.utils.pkg, "clear_rtag", Mock()): result = zypper.refresh_db() self.assertEqual(result.get("openSUSE-Leap-42.1-LATEST"), False) self.assertEqual(result.get("openSUSE-Leap-42.1-Update"), False) self.assertEqual(result.get("openSUSE-Leap-42.1-Update-Non-Oss"), True) zypper_mock.assert_called_with( ["zypper", "--non-interactive", "refresh", "--force"], **call_kwargs ) zypper.refresh_db(force=False) zypper_mock.assert_called_with( ["zypper", "--non-interactive", "refresh"], **call_kwargs ) zypper.refresh_db(force=True) zypper_mock.assert_called_with( ["zypper", "--non-interactive", "refresh", "--force"], **call_kwargs ) zypper.refresh_db(gpgautoimport=True) zypper_mock.assert_called_with( [ "zypper", "--non-interactive", "--gpg-auto-import-keys", "refresh", "--force", ], **call_kwargs, ) zypper.refresh_db(gpgautoimport=True, force=True) zypper_mock.assert_called_with( [ "zypper", "--non-interactive", "--gpg-auto-import-keys", "refresh", "--force", ], **call_kwargs, ) zypper.refresh_db(gpgautoimport=True, force=False) zypper_mock.assert_called_with( [ "zypper", "--non-interactive", "--gpg-auto-import-keys", "refresh", ], **call_kwargs, ) zypper.refresh_db( gpgautoimport=True, refresh=True, repos="mock-repo-name", root=None, url="http://repo.url/some/path", ) zypper_mock.assert_called_with( [ "zypper", "--non-interactive", "--gpg-auto-import-keys", "refresh", "--force", "mock-repo-name", ], **call_kwargs, ) zypper.refresh_db( gpgautoimport=True, repos="mock-repo-name", root=None, url="http://repo.url/some/path", ) zypper_mock.assert_called_with( [ "zypper", "--non-interactive", "--gpg-auto-import-keys", "refresh", "--force", "mock-repo-name", ], **call_kwargs, ) def test_info_installed(self): """ Test the return information of the named package(s), installed on the system. :return: """ run_out = { "virgo-dummy": { "build_date": "2015-07-09T10:55:19Z", "vendor": "openSUSE Build Service", "description": ( "This is the Virgo dummy package used for testing SUSE Manager" ), "license": "GPL-2.0", "build_host": "sheep05", "url": "http://www.suse.com", "build_date_time_t": 1436432119, "relocations": "(not relocatable)", "source_rpm": "virgo-dummy-1.0-1.1.src.rpm", "install_date": "2016-02-23T16:31:57Z", "install_date_time_t": 1456241517, "summary": "Virgo dummy package", "version": "1.0", "signature": ( "DSA/SHA1, Thu Jul 9 08:55:33 2015, Key ID 27fa41bd8a7c64f9" ), "release": "1.1", "group": "Applications/System", "arch": "noarch", "size": "17992", }, "libopenssl1_0_0": { "build_date": "2015-11-04T23:20:34Z", "vendor": "SUSE LLC <https://www.suse.com/>", "description": "The OpenSSL Project is a collaborative effort.", "license": "OpenSSL", "build_host": "sheep11", "url": "https://www.openssl.org/", "build_date_time_t": 1446675634, "relocations": "(not relocatable)", "source_rpm": "openssl-1.0.1i-34.1.src.rpm", "install_date": "2016-02-23T16:31:35Z", "install_date_time_t": 1456241495, "summary": "Secure Sockets and Transport Layer Security", "version": "1.0.1i", "signature": ( "RSA/SHA256, Wed Nov 4 22:21:34 2015, Key ID 70af9e8139db7c82" ), "release": "34.1", "group": "Productivity/Networking/Security", "packager": "https://www.suse.com/", "arch": "x86_64", "size": "2576912", }, } with patch.dict( zypper.__salt__, {"lowpkg.info": MagicMock(return_value=run_out)} ): installed = zypper.info_installed() # Test overall products length self.assertEqual(len(installed), 2) # Test translated fields for pkg_name, pkg_info in installed.items(): self.assertEqual( installed[pkg_name].get("source"), run_out[pkg_name]["source_rpm"] ) # Test keys transition from the lowpkg.info for pn_key, pn_val in run_out["virgo-dummy"].items(): if pn_key == "source_rpm": continue self.assertEqual(installed["virgo-dummy"][pn_key], pn_val) def test_info_installed_with_non_ascii_char(self): """ Test the return information of the named package(s), installed on the system whith non-ascii chars :return: """ run_out = {"vīrgô": {"description": "vīrgô d€šçripţiǫñ"}} with patch.dict( zypper.__salt__, {"lowpkg.info": MagicMock(return_value=run_out)} ): installed = zypper.info_installed() self.assertEqual(installed["vīrgô"]["description"], "vīrgô d€šçripţiǫñ") def test_info_installed_with_all_versions(self): """ Test the return information of all versions for the named package(s), installed on the system. :return: """ run_out = { "virgo-dummy": [ { "build_date": "2015-07-09T10:55:19Z", "vendor": "openSUSE Build Service", "description": ( "This is the Virgo dummy package used for testing SUSE Manager" ), "license": "GPL-2.0", "build_host": "sheep05", "url": "http://www.suse.com", "build_date_time_t": 1436432119, "relocations": "(not relocatable)", "source_rpm": "virgo-dummy-1.0-1.1.src.rpm", "install_date": "2016-02-23T16:31:57Z", "install_date_time_t": 1456241517, "summary": "Virgo dummy package", "version": "1.0", "signature": ( "DSA/SHA1, Thu Jul 9 08:55:33 2015, Key ID 27fa41bd8a7c64f9" ), "release": "1.1", "group": "Applications/System", "arch": "i686", "size": "17992", }, { "build_date": "2015-07-09T10:15:19Z", "vendor": "openSUSE Build Service", "description": ( "This is the Virgo dummy package used for testing SUSE Manager" ), "license": "GPL-2.0", "build_host": "sheep05", "url": "http://www.suse.com", "build_date_time_t": 1436432119, "relocations": "(not relocatable)", "source_rpm": "virgo-dummy-1.0-1.1.src.rpm", "install_date": "2016-02-23T16:31:57Z", "install_date_time_t": 14562415127, "summary": "Virgo dummy package", "version": "1.0", "signature": ( "DSA/SHA1, Thu Jul 9 08:55:33 2015, Key ID 27fa41bd8a7c64f9" ), "release": "1.1", "group": "Applications/System", "arch": "x86_64", "size": "13124", }, ], "libopenssl1_0_0": [ { "build_date": "2015-11-04T23:20:34Z", "vendor": "SUSE LLC <https://www.suse.com/>", "description": "The OpenSSL Project is a collaborative effort.", "license": "OpenSSL", "build_host": "sheep11", "url": "https://www.openssl.org/", "build_date_time_t": 1446675634, "relocations": "(not relocatable)", "source_rpm": "openssl-1.0.1i-34.1.src.rpm", "install_date": "2016-02-23T16:31:35Z", "install_date_time_t": 1456241495, "summary": "Secure Sockets and Transport Layer Security", "version": "1.0.1i", "signature": ( "RSA/SHA256, Wed Nov 4 22:21:34 2015, Key ID 70af9e8139db7c82" ), "release": "34.1", "group": "Productivity/Networking/Security", "packager": "https://www.suse.com/", "arch": "x86_64", "size": "2576912", } ], } with patch.dict( zypper.__salt__, {"lowpkg.info": MagicMock(return_value=run_out)} ): installed = zypper.info_installed(all_versions=True) # Test overall products length self.assertEqual(len(installed), 2) # Test multiple versions for the same package for pkg_name, pkg_info_list in installed.items(): self.assertEqual( len(pkg_info_list), 2 if pkg_name == "virgo-dummy" else 1 ) for info in pkg_info_list: self.assertTrue(info["arch"] in ("x86_64", "i686")) def test_info_available(self): """ Test return the information of the named package available for the system. :return: """ test_pkgs = ["vim", "emacs", "python"] with patch( "salt.modules.zypperpkg.__zypper__", ZyppCallMock(return_value=get_test_data("zypper-available.txt")), ): available = zypper.info_available(*test_pkgs, refresh=False) self.assertEqual(len(available), 3) for pkg_name, pkg_info in available.items(): self.assertIn(pkg_name, test_pkgs) self.assertEqual(available["emacs"]["status"], "up-to-date") self.assertTrue(available["emacs"]["installed"]) self.assertEqual(available["emacs"]["support level"], "Level 3") self.assertEqual( available["emacs"]["vendor"], "SUSE LLC <https://www.suse.com/>" ) self.assertEqual(available["emacs"]["summary"], "GNU Emacs Base Package") self.assertEqual(available["vim"]["status"], "not installed") self.assertFalse(available["vim"]["installed"]) self.assertEqual(available["vim"]["support level"], "Level 3") self.assertEqual( available["vim"]["vendor"], "SUSE LLC <https://www.suse.com/>" ) self.assertEqual(available["vim"]["summary"], "Vi IMproved") def test_latest_version(self): """ Test the latest version of the named package available for upgrade or installation. :return: """ with patch( "salt.modules.zypperpkg.__zypper__", ZyppCallMock(return_value=get_test_data("zypper-available.txt")), ), patch("salt.modules.zypperpkg.refresh_db", MagicMock(return_value=True)): self.assertEqual(zypper.latest_version("vim"), "7.4.326-2.62") self.assertDictEqual( zypper.latest_version("vim", "fakepkg"), {"vim": "7.4.326-2.62", "fakepkg": ""}, ) def test_upgrade_kernel(self): """ Test kernel package upgrade success. :return: """ with patch.dict(zypper.__grains__, {"osrelease_info": [12, 1]}), patch( "salt.modules.zypperpkg.refresh_db", MagicMock(return_value=True) ), patch( "salt.modules.zypperpkg._systemd_scope", MagicMock(return_value=False) ): with patch.dict( zypper.__salt__, { "pkg_resource.parse_targets": MagicMock( return_value=(["kernel-default"], None) ) }, ): with patch( "salt.modules.zypperpkg.__zypper__.noraise.call", MagicMock() ): with patch( "salt.modules.zypperpkg.list_pkgs", MagicMock( side_effect=[ {"kernel-default": "3.12.49-11.1"}, {"kernel-default": "3.12.49-11.1,3.12.51-60.20.2"}, ] ), ): ret = zypper.install( "kernel-default", "--auto-agree-with-licenses" ) self.assertDictEqual( ret, { "kernel-default": { "old": "3.12.49-11.1", "new": "3.12.49-11.1,3.12.51-60.20.2", } }, ) def test_upgrade_available(self): """ Test whether or not an upgrade is available for a given package. :return: """ ref_out = get_test_data("zypper-available.txt") with patch( "salt.modules.zypperpkg.__zypper__", ZyppCallMock(return_value=get_test_data("zypper-available.txt")), ), patch("salt.modules.zypperpkg.refresh_db", MagicMock(return_value=True)): for pkg_name in ["emacs", "python"]: self.assertFalse(zypper.upgrade_available(pkg_name)) self.assertTrue(zypper.upgrade_available("vim")) def test_list_pkgs(self): """ Test packages listing. :return: """ def _add_data(data, key, value): data.setdefault(key, []).append(value) rpm_out = [ "protobuf-java_|-(none)_|-2.6.1_|-3.1.develHead_|-noarch_|-(none)_|-1499257756", "yast2-ftp-server_|-(none)_|-3.1.8_|-8.1_|-x86_64_|-(none)_|-1499257798", "jose4j_|-(none)_|-0.4.4_|-2.1.develHead_|-noarch_|-(none)_|-1499257756", "apache-commons-cli_|-(none)_|-1.2_|-1.233_|-noarch_|-(none)_|-1498636510", "jakarta-commons-discovery_|-(none)_|-0.4_|-129.686_|-noarch_|-(none)_|-1498636511", "susemanager-build-keys-web_|-(none)_|-12.0_|-5.1.develHead_|-noarch_|-(none)_|-1498636510", "gpg-pubkey_|-(none)_|-39db7c82_|-5847eb1f_|-(none)_|-(none)_|-1519203802", "gpg-pubkey_|-(none)_|-8a7c64f9_|-5aaa93ca_|-(none)_|-(none)_|-1529925595", "kernel-default_|-(none)_|-4.4.138_|-94.39.1_|-x86_64_|-(none)_|-1529936067", "kernel-default_|-(none)_|-4.4.73_|-5.1_|-x86_64_|-(none)_|-1503572639", "perseus-dummy_|-(none)_|-1.1_|-1.1_|-i586_|-(none)_|-1529936062", ] with patch.dict(zypper.__grains__, {"osarch": "x86_64"}), patch.dict( zypper.__salt__, {"cmd.run": MagicMock(return_value=os.linesep.join(rpm_out))}, ), patch.dict(zypper.__salt__, {"pkg_resource.add_pkg": _add_data}), patch.dict( zypper.__salt__, {"pkg_resource.format_pkg_list": pkg_resource.format_pkg_list}, ), patch.dict( zypper.__salt__, {"pkg_resource.stringify": MagicMock()} ): pkgs = zypper.list_pkgs(versions_as_list=True) self.assertFalse(pkgs.get("gpg-pubkey", False)) self.assertTrue("pkg.list_pkgs_None_[]" in zypper.__context__) for pkg_name, pkg_version in { "jakarta-commons-discovery": ["0.4-129.686"], "yast2-ftp-server": ["3.1.8-8.1"], "protobuf-java": ["2.6.1-3.1.develHead"], "susemanager-build-keys-web": ["12.0-5.1.develHead"], "apache-commons-cli": ["1.2-1.233"], "kernel-default": ["4.4.138-94.39.1", "4.4.73-5.1"], "perseus-dummy.i586": ["1.1-1.1"], "jose4j": ["0.4.4-2.1.develHead"], }.items(): self.assertTrue(pkgs.get(pkg_name)) self.assertEqual(pkgs[pkg_name], pkg_version) def test_list_pkgs_no_context(self): """ Test packages listing. :return: """ def _add_data(data, key, value): data.setdefault(key, []).append(value) rpm_out = [ "protobuf-java_|-(none)_|-2.6.1_|-3.1.develHead_|-noarch_|-(none)_|-1499257756", "yast2-ftp-server_|-(none)_|-3.1.8_|-8.1_|-x86_64_|-(none)_|-1499257798", "jose4j_|-(none)_|-0.4.4_|-2.1.develHead_|-noarch_|-(none)_|-1499257756", "apache-commons-cli_|-(none)_|-1.2_|-1.233_|-noarch_|-(none)_|-1498636510", "jakarta-commons-discovery_|-(none)_|-0.4_|-129.686_|-noarch_|-(none)_|-1498636511", "susemanager-build-keys-web_|-(none)_|-12.0_|-5.1.develHead_|-noarch_|-(none)_|-1498636510", "gpg-pubkey_|-(none)_|-39db7c82_|-5847eb1f_|-(none)_|-(none)_|-1519203802", "gpg-pubkey_|-(none)_|-8a7c64f9_|-5aaa93ca_|-(none)_|-(none)_|-1529925595", "kernel-default_|-(none)_|-4.4.138_|-94.39.1_|-x86_64_|-(none)_|-1529936067", "kernel-default_|-(none)_|-4.4.73_|-5.1_|-x86_64_|-(none)_|-1503572639", "perseus-dummy_|-(none)_|-1.1_|-1.1_|-i586_|-(none)_|-1529936062", ] with patch.dict(zypper.__grains__, {"osarch": "x86_64"}), patch.dict( zypper.__salt__, {"cmd.run": MagicMock(return_value=os.linesep.join(rpm_out))}, ), patch.dict(zypper.__salt__, {"pkg_resource.add_pkg": _add_data}), patch.dict( zypper.__salt__, {"pkg_resource.format_pkg_list": pkg_resource.format_pkg_list}, ), patch.dict( zypper.__salt__, {"pkg_resource.stringify": MagicMock()} ), patch.object( zypper, "_list_pkgs_from_context" ) as list_pkgs_context_mock: pkgs = zypper.list_pkgs(versions_as_list=True, use_context=False) list_pkgs_context_mock.assert_not_called() list_pkgs_context_mock.reset_mock() pkgs = zypper.list_pkgs(versions_as_list=True, use_context=False) list_pkgs_context_mock.assert_not_called() list_pkgs_context_mock.reset_mock() def test_list_pkgs_with_attr(self): """ Test packages listing with the attr parameter :return: """ def _add_data(data, key, value): data.setdefault(key, []).append(value) rpm_out = [ "protobuf-java_|-(none)_|-2.6.1_|-3.1.develHead_|-noarch_|-(none)_|-1499257756", "yast2-ftp-server_|-(none)_|-3.1.8_|-8.1_|-x86_64_|-(none)_|-1499257798", "jose4j_|-(none)_|-0.4.4_|-2.1.develHead_|-noarch_|-(none)_|-1499257756", "apache-commons-cli_|-(none)_|-1.2_|-1.233_|-noarch_|-(none)_|-1498636510", "jakarta-commons-discovery_|-(none)_|-0.4_|-129.686_|-noarch_|-(none)_|-1498636511", "susemanager-build-keys-web_|-(none)_|-12.0_|-5.1.develHead_|-noarch_|-(none)_|-1498636510", "gpg-pubkey_|-(none)_|-39db7c82_|-5847eb1f_|-(none)_|-(none)_|-1519203802", "gpg-pubkey_|-(none)_|-8a7c64f9_|-5aaa93ca_|-(none)_|-(none)_|-1529925595", "kernel-default_|-(none)_|-4.4.138_|-94.39.1_|-x86_64_|-(none)_|-1529936067", "kernel-default_|-(none)_|-4.4.73_|-5.1_|-x86_64_|-(none)_|-1503572639", "perseus-dummy_|-(none)_|-1.1_|-1.1_|-i586_|-(none)_|-1529936062", ] with patch.dict( zypper.__salt__, {"cmd.run": MagicMock(return_value=os.linesep.join(rpm_out))}, ), patch.dict(zypper.__grains__, {"osarch": "x86_64"}), patch.dict( zypper.__salt__, {"pkg_resource.add_pkg": _add_data} ), patch.dict( zypper.__salt__, {"pkg_resource.format_pkg_list": pkg_resource.format_pkg_list}, ), patch.dict( zypper.__salt__, {"pkg_resource.stringify": MagicMock()} ), patch.dict( pkg_resource.__salt__, {"pkg.parse_arch": zypper.parse_arch} ): pkgs = zypper.list_pkgs( attr=["epoch", "release", "arch", "install_date_time_t"] ) self.assertFalse(pkgs.get("gpg-pubkey", False)) self.assertTrue("pkg.list_pkgs_None_[]" in zypper.__context__) for pkg_name, pkg_attr in { "jakarta-commons-discovery": [ { "version": "0.4", "release": "129.686", "arch": "noarch", "install_date_time_t": 1498636511, "epoch": None, } ], "yast2-ftp-server": [ { "version": "3.1.8", "release": "8.1", "arch": "x86_64", "install_date_time_t": 1499257798, "epoch": None, } ], "protobuf-java": [ { "version": "2.6.1", "release": "3.1.develHead", "install_date_time_t": 1499257756, "arch": "noarch", "epoch": None, } ], "susemanager-build-keys-web": [ { "version": "12.0", "release": "5.1.develHead", "arch": "noarch", "install_date_time_t": 1498636510, "epoch": None, } ], "apache-commons-cli": [ { "version": "1.2", "release": "1.233", "arch": "noarch", "install_date_time_t": 1498636510, "epoch": None, } ], "kernel-default": [ { "version": "4.4.138", "release": "94.39.1", "arch": "x86_64", "install_date_time_t": 1529936067, "epoch": None, }, { "version": "4.4.73", "release": "5.1", "arch": "x86_64", "install_date_time_t": 1503572639, "epoch": None, }, ], "perseus-dummy": [ { "version": "1.1", "release": "1.1", "arch": "i586", "install_date_time_t": 1529936062, "epoch": None, } ], "jose4j": [ { "arch": "noarch", "version": "0.4.4", "release": "2.1.develHead", "install_date_time_t": 1499257756, "epoch": None, } ], }.items(): self.assertTrue(pkgs.get(pkg_name)) self.assertEqual(pkgs[pkg_name], pkg_attr) def test_list_pkgs_with_attr_multiple_versions(self): """ Test packages listing with the attr parameter reporting multiple version installed :return: """ def _add_data(data, key, value): data.setdefault(key, []).append(value) rpm_out = [ "glibc_|-2.12_|-1.212.el6_|-i686_|-_|-1542394210", "glibc_|-2.12_|-1.212.el6_|-x86_64_|-_|-1542394204", "virt-what_|-1.13_|-8.el7_|-x86_64_|-_|-1487838486", "virt-what_|-1.10_|-2.el7_|-x86_64_|-_|-1387838486", ] with patch.dict(zypper.__grains__, {"osarch": "x86_64"}), patch.dict( zypper.__salt__, {"cmd.run": MagicMock(return_value=os.linesep.join(rpm_out))}, ), patch.dict(zypper.__salt__, {"pkg_resource.add_pkg": _add_data}), patch.dict( zypper.__salt__, {"pkg_resource.format_pkg_list": pkg_resource.format_pkg_list}, ), patch.dict( zypper.__salt__, {"pkg_resource.stringify": MagicMock()} ), patch.dict( pkg_resource.__salt__, {"pkg.parse_arch": zypper.parse_arch} ): pkgs = zypper.list_pkgs( attr=["epoch", "release", "arch", "install_date_time_t"] ) expected_pkg_list = { "glibc": [ { "version": "2.12", "release": "1.212.el6", "install_date_time_t": 1542394210, "arch": "i686", "epoch": None, }, { "version": "2.12", "release": "1.212.el6", "install_date_time_t": 1542394204, "arch": "x86_64", "epoch": None, }, ], "virt-what": [ { "version": "1.10", "release": "2.el7", "install_date_time_t": 1387838486, "arch": "x86_64", "epoch": None, }, { "version": "1.13", "release": "8.el7", "install_date_time_t": 1487838486, "arch": "x86_64", "epoch": None, }, ], } for pkgname, pkginfo in pkgs.items(): self.assertCountEqual(pkginfo, expected_pkg_list[pkgname]) def test_list_patches(self): """ Test advisory patches listing. :return: """ ref_out = { "stdout": get_test_data("zypper-patches.xml"), "stderr": None, "retcode": 0, } PATCHES_RET = { "SUSE-SLE-SERVER-12-SP2-2017-97": { "installed": False, "summary": "Recommended update for ovmf", }, "SUSE-SLE-SERVER-12-SP2-2017-98": { "installed": True, "summary": "Recommended update for kmod", }, "SUSE-SLE-SERVER-12-SP2-2017-99": { "installed": False, "summary": "Security update for apache2", }, } with patch.dict( zypper.__salt__, {"cmd.run_all": MagicMock(return_value=ref_out)} ): list_patches = zypper.list_patches(refresh=False) self.assertEqual(len(list_patches), 3) self.assertDictEqual(list_patches, PATCHES_RET) @patch( "salt.utils.path.os_walk", MagicMock(return_value=[("test", "test", "test")]) ) @patch("os.path.getsize", MagicMock(return_value=123456)) @patch("os.path.getctime", MagicMock(return_value=1234567890.123456)) @patch( "fnmatch.filter", MagicMock(return_value=["/var/cache/zypper/packages/foo/bar/test_package.rpm"]), ) def test_list_downloaded_with_kwargs(self): """ Test downloaded packages listing. :return: """ DOWNLOADED_RET = { "test-package": { "1.0": { "path": "/var/cache/zypper/packages/foo/bar/test_package.rpm", "size": 123456, "creation_date_time_t": 1234567890, "creation_date_time": "2009-02-13T23:31:30", } } } with patch.dict( zypper.__salt__, { "lowpkg.bin_pkg_info": MagicMock( return_value={"name": "test-package", "version": "1.0"} ) }, ): list_downloaded = zypper.list_downloaded(kw1=True, kw2=False) self.assertEqual(len(list_downloaded), 1) self.assertDictEqual(list_downloaded, DOWNLOADED_RET) @patch( "salt.utils.path.os_walk", MagicMock(return_value=[("test", "test", "test")]) ) @patch("os.path.getsize", MagicMock(return_value=123456)) @patch("os.path.getctime", MagicMock(return_value=1234567890.123456)) @patch( "fnmatch.filter", MagicMock(return_value=["/var/cache/zypper/packages/foo/bar/test_package.rpm"]), ) def test_list_downloaded(self): """ Test downloaded packages listing. :return: """ DOWNLOADED_RET = { "test-package": { "1.0": { "path": "/var/cache/zypper/packages/foo/bar/test_package.rpm", "size": 123456, "creation_date_time_t": 1234567890, "creation_date_time": "2009-02-13T23:31:30", } } } with patch.dict( zypper.__salt__, { "lowpkg.bin_pkg_info": MagicMock( return_value={"name": "test-package", "version": "1.0"} ) }, ): list_downloaded = zypper.list_downloaded() self.assertEqual(len(list_downloaded), 1) self.assertDictEqual(list_downloaded, DOWNLOADED_RET) def test_download(self): """ Test package download :return: """ download_out = { "stdout": get_test_data("zypper-download.xml"), "stderr": None, "retcode": 0, } test_out = { "nmap": { "path": "/var/cache/zypp/packages/SLE-12-x86_64-Pool/x86_64/nmap-6.46-1.72.x86_64.rpm", "repository-alias": "SLE-12-x86_64-Pool", "repository-name": "SLE-12-x86_64-Pool", } } with patch.dict( zypper.__salt__, {"cmd.run_all": MagicMock(return_value=download_out)} ): with patch.dict( zypper.__salt__, {"lowpkg.checksum": MagicMock(return_value=True)} ): self.assertEqual(zypper.download("nmap"), test_out) test_out["_error"] = "The following package(s) failed to download: foo" self.assertEqual(zypper.download("nmap", "foo"), test_out) @patch("salt.modules.zypperpkg._systemd_scope", MagicMock(return_value=False)) @patch( "salt.modules.zypperpkg.list_downloaded", MagicMock( side_effect=[ {}, { "vim": { "1.1": { "path": "/foo/bar/test.rpm", "size": 1234, "creation_date_time_t": 1234567890, "creation_date_time": "2009-02-13T23:31:30", } } }, ] ), ) def test_install_with_downloadonly(self): """ Test a package installation with downloadonly=True. :return: """ with patch.dict( zypper.__salt__, { "pkg_resource.parse_targets": MagicMock( return_value=({"vim": None}, "repository") ) }, ): with patch( "salt.modules.zypperpkg.__zypper__.noraise.call", MagicMock() ) as zypper_mock: ret = zypper.install(pkgs=["vim"], downloadonly=True) zypper_mock.assert_called_once_with( "--no-refresh", "install", "--auto-agree-with-licenses", "--name", "--download-only", "vim", ) self.assertDictEqual( ret, { "vim": { "new": { "1.1": { "path": "/foo/bar/test.rpm", "size": 1234, "creation_date_time_t": 1234567890, "creation_date_time": "2009-02-13T23:31:30", } }, "old": "", } }, ) @patch("salt.modules.zypperpkg._systemd_scope", MagicMock(return_value=False)) @patch( "salt.modules.zypperpkg.list_downloaded", MagicMock( return_value={ "vim": { "1.1": { "path": "/foo/bar/test.rpm", "size": 1234, "creation_date_time_t": 1234567890, "creation_date_time": "2017-01-01T11:00:00", } } } ), ) def test_install_with_downloadonly_already_downloaded(self): """ Test a package installation with downloadonly=True when package is already downloaded. :return: """ with patch.dict( zypper.__salt__, { "pkg_resource.parse_targets": MagicMock( return_value=({"vim": None}, "repository") ) }, ): with patch( "salt.modules.zypperpkg.__zypper__.noraise.call", MagicMock() ) as zypper_mock: ret = zypper.install(pkgs=["vim"], downloadonly=True) zypper_mock.assert_called_once_with( "--no-refresh", "install", "--auto-agree-with-licenses", "--name", "--download-only", "vim", ) self.assertDictEqual(ret, {}) @patch("salt.modules.zypperpkg._systemd_scope", MagicMock(return_value=False)) @patch( "salt.modules.zypperpkg._get_patches", MagicMock( return_value={"SUSE-PATCH-1234": {"installed": False, "summary": "test"}} ), ) @patch( "salt.modules.zypperpkg.list_pkgs", MagicMock(side_effect=[{"vim": "1.1"}, {"vim": "1.2"}]), ) def test_install_advisory_patch_ok(self): """ Test successfully advisory patch installation. :return: """ with patch.dict( zypper.__salt__, { "pkg_resource.parse_targets": MagicMock( return_value=({"SUSE-PATCH-1234": None}, "advisory") ) }, ): with patch( "salt.modules.zypperpkg.__zypper__.noraise.call", MagicMock() ) as zypper_mock: ret = zypper.install(advisory_ids=["SUSE-PATCH-1234"]) zypper_mock.assert_called_once_with( "--no-refresh", "install", "--auto-agree-with-licenses", "--name", "patch:SUSE-PATCH-1234", ) self.assertDictEqual(ret, {"vim": {"old": "1.1", "new": "1.2"}}) @patch("salt.modules.zypperpkg._systemd_scope", MagicMock(return_value=False)) @patch( "salt.modules.zypperpkg._get_patches", MagicMock( return_value={"SUSE-PATCH-1234": {"installed": False, "summary": "test"}} ), ) @patch("salt.modules.zypperpkg.list_pkgs", MagicMock(return_value={"vim": "1.1"})) def test_install_advisory_patch_failure(self): """ Test failing advisory patch installation because patch does not exist. :return: """ with patch.dict( zypper.__salt__, { "pkg_resource.parse_targets": MagicMock( return_value=({"SUSE-PATCH-XXX": None}, "advisory") ) }, ): with patch( "salt.modules.zypperpkg.__zypper__.noraise.call", MagicMock() ) as zypper_mock: with self.assertRaisesRegex( CommandExecutionError, '^Advisory id "SUSE-PATCH-XXX" not found$' ): zypper.install(advisory_ids=["SUSE-PATCH-XXX"]) @patch("salt.modules.zypperpkg._systemd_scope", MagicMock(return_value=False)) @patch( "salt.modules.zypperpkg.list_products", MagicMock(return_value={"openSUSE": {"installed": False, "summary": "test"}}), ) @patch( "salt.modules.zypperpkg.list_pkgs", MagicMock( side_effect=[{"product:openSUSE": "15.2"}, {"product:openSUSE": "15.3"}] ), ) def test_install_product_ok(self): """ Test successfully product installation. """ with patch.dict( zypper.__salt__, { "pkg_resource.parse_targets": MagicMock( return_value=(["product:openSUSE"], None) ) }, ): with patch( "salt.modules.zypperpkg.__zypper__.noraise.call", MagicMock() ) as zypper_mock: ret = zypper.install("product:openSUSE", includes=["product"]) zypper_mock.assert_called_once_with( "--no-refresh", "install", "--auto-agree-with-licenses", "--name", "product:openSUSE", ) self.assertDictEqual( ret, {"product:openSUSE": {"old": "15.2", "new": "15.3"}} ) def test_remove_purge(self): """ Test package removal :return: """ class ListPackages: def __init__(self): self._packages = ["vim", "pico"] self._pkgs = { "vim": "0.18.0", "emacs": "24.0.1", "pico": "0.1.1", } def __call__(self, root=None, includes=None): pkgs = self._pkgs.copy() for target in self._packages: if self._pkgs.get(target): del self._pkgs[target] return pkgs parsed_targets = [{"vim": None, "pico": None}, None] cmd_out = {"retcode": 0, "stdout": "", "stderr": ""} # If config.get starts being used elsewhere, we'll need to write a # side_effect function. patches = { "cmd.run": MagicMock(return_value="vim: vi vim\npico: pico"), "cmd.run_all": MagicMock(return_value=cmd_out), "pkg_resource.parse_targets": MagicMock(return_value=parsed_targets), "pkg_resource.stringify": MagicMock(), "config.get": MagicMock(return_value=True), } with patch.dict(zypper.__salt__, patches): with patch("salt.modules.zypperpkg.list_pkgs", ListPackages()): diff = zypper.remove(name="vim,pico") for pkg_name in ["vim", "pico"]: self.assertTrue(diff.get(pkg_name)) self.assertTrue(diff[pkg_name]["old"]) self.assertFalse(diff[pkg_name]["new"]) def test_repo_value_info(self): """ Tests if repo info is properly parsed. :return: """ repos_cfg = configparser.ConfigParser() for cfg in ["zypper-repo-1.cfg", "zypper-repo-2.cfg"]: repos_cfg.read_file(io.StringIO(get_test_data(cfg))) for alias in repos_cfg.sections(): r_info = zypper._get_repo_info(alias, repos_cfg=repos_cfg) self.assertEqual(type(r_info["type"]), type(None)) self.assertEqual(type(r_info["enabled"]), bool) self.assertEqual(type(r_info["autorefresh"]), bool) self.assertEqual(type(r_info["baseurl"]), str) self.assertEqual(type(r_info["name"]), str) self.assertEqual(r_info["type"], None) self.assertEqual(r_info["enabled"], alias == "SLE12-SP1-x86_64-Update") self.assertEqual(r_info["autorefresh"], alias == "SLE12-SP1-x86_64-Update") def test_repo_add_mod_name(self): """ Test mod_repo adds the new repo and call modify to update descriptive name. :return: """ url = self.new_repo_config["url"] name = self.new_repo_config["name"] desc_name = "Update Repository" zypper_patcher = patch.multiple( "salt.modules.zypperpkg", **self.zypper_patcher_config ) with zypper_patcher: zypper.mod_repo(name, **{"url": url, "name": desc_name}) self.assertEqual( zypper.__zypper__(root=None).xml.call.call_args_list, [call("ar", url, name)], ) zypper.__zypper__(root=None).refreshable.xml.call.assert_called_once_with( "mr", "--name", desc_name, name ) def test_repo_add_nomod_noref(self): """ Test mod_repo adds the new repo and nothing else :return: """ zypper_patcher = patch.multiple( "salt.modules.zypperpkg", **self.zypper_patcher_config ) url = self.new_repo_config["url"] name = self.new_repo_config["name"] with zypper_patcher: zypper.mod_repo(name, **{"url": url}) self.assertEqual( zypper.__zypper__(root=None).xml.call.call_args_list, [call("ar", url, name)], ) self.assertTrue( zypper.__zypper__(root=None).refreshable.xml.call.call_count == 0 ) def test_repo_noadd_nomod_noref(self): """ Test mod_repo detects the repo already exists, no modification was requested and no refresh requested either :return: """ url = self.new_repo_config["url"] name = self.new_repo_config["name"] self.zypper_patcher_config["_get_configured_repos"] = Mock( **{"return_value.sections.return_value": [name]} ) zypper_patcher = patch.multiple( "salt.modules.zypperpkg", **self.zypper_patcher_config ) with zypper_patcher: out = zypper.mod_repo(name, alias="new-alias") self.assertEqual( out["comment"], "Specified arguments did not result in modification of repo", ) self.assertTrue(zypper.__zypper__(root=None).xml.call.call_count == 0) self.assertTrue( zypper.__zypper__(root=None).refreshable.xml.call.call_count == 0 ) def test_repo_noadd_modbaseurl_ref(self): """ Test mod_repo detects the repo already exists, no modification was requested and no refresh requested either :return: """ url = self.new_repo_config["url"] name = self.new_repo_config["name"] self.zypper_patcher_config["_get_configured_repos"] = Mock( **{"return_value.sections.side_effect": [[name], [], [], [name]]} ) zypper_patcher = patch.multiple( "salt.modules.zypperpkg", **self.zypper_patcher_config ) with zypper_patcher: params = {"baseurl": url + "-changed", "enabled": False} zypper.mod_repo(name, **params) expected_params = { "alias": "mock-repo-name", "name": "mock-repo-name", "autorefresh": True, "baseurl": "http://repo.url/some/path-changed", "enabled": False, "priority": 1, "cache": False, "keeppackages": False, "type": "rpm-md", "root": None, } self.assertEqual(zypper.mod_repo.call_count, 2) self.assertEqual( zypper.mod_repo.mock_calls[1], call(name, **expected_params) ) def test_repo_add_mod_noref(self): """ Test mod_repo adds the new repo and call modify to update autorefresh :return: """ zypper_patcher = patch.multiple( "salt.modules.zypperpkg", **self.zypper_patcher_config ) url = self.new_repo_config["url"] name = self.new_repo_config["name"] with zypper_patcher: zypper.mod_repo(name, **{"url": url, "refresh": True}) self.assertEqual( zypper.__zypper__(root=None).xml.call.call_args_list, [call("ar", url, name)], ) zypper.__zypper__(root=None).refreshable.xml.call.assert_called_once_with( "mr", "--refresh", name ) def test_repo_noadd_mod_noref(self): """ Test mod_repo detects the repository exists, calls modify to update 'autorefresh' but does not call refresh :return: """ url = self.new_repo_config["url"] name = self.new_repo_config["name"] self.zypper_patcher_config["_get_configured_repos"] = Mock( **{"return_value.sections.return_value": [name]} ) zypper_patcher = patch.multiple( "salt.modules.zypperpkg", **self.zypper_patcher_config ) with zypper_patcher: zypper.mod_repo(name, **{"url": url, "refresh": True}) self.assertTrue(zypper.__zypper__(root=None).xml.call.call_count == 0) zypper.__zypper__(root=None).refreshable.xml.call.assert_called_once_with( "mr", "--refresh", name ) def test_repo_add_nomod_ref(self): """ Test mod_repo adds the new repo and refreshes the repo with `zypper --gpg-auto-import-keys refresh <repo-name>` :return: """ zypper_patcher = patch.multiple( "salt.modules.zypperpkg", **self.zypper_patcher_config ) url = self.new_repo_config["url"] name = self.new_repo_config["name"] with zypper_patcher, patch.object(zypper, "refresh_db", Mock()) as refreshmock: zypper.mod_repo(name, **{"url": url, "gpgautoimport": True}) self.assertEqual( zypper.__zypper__(root=None).xml.call.call_args_list, [ call("ar", url, name), ], ) self.assertTrue( zypper.__zypper__(root=None).refreshable.xml.call.call_count == 0 ) refreshmock.assert_called_once_with( gpgautoimport=True, repos=name, root=None, url="http://repo.url/some/path", ) def test_repo_noadd_nomod_ref(self): """ Test mod_repo detects the repo already exists, has nothing to modify and refreshes the repo with `zypper --gpg-auto-import-keys refresh <repo-name>` :return: """ url = self.new_repo_config["url"] name = self.new_repo_config["name"] self.zypper_patcher_config["_get_configured_repos"] = Mock( **{"return_value.sections.return_value": [name]} ) zypper_patcher = patch.multiple( "salt.modules.zypperpkg", **self.zypper_patcher_config ) with zypper_patcher, patch.object(zypper, "refresh_db", Mock()) as refreshmock: zypper.mod_repo(name, **{"url": url, "gpgautoimport": True}) self.assertTrue( zypper.__zypper__(root=None).refreshable.xml.call.call_count == 0 ) refreshmock.assert_called_once_with( gpgautoimport=True, repos=name, root=None, url="http://repo.url/some/path", ) def test_repo_add_mod_ref(self): """ Test mod_repo adds the new repo, calls modify to update 'autorefresh' and refreshes the repo with `zypper --gpg-auto-import-keys refresh <repo-name>` :return: """ zypper_patcher = patch.multiple( "salt.modules.zypperpkg", **self.zypper_patcher_config ) url = self.new_repo_config["url"] name = self.new_repo_config["name"] with zypper_patcher, patch.object(zypper, "refresh_db", Mock()) as refreshmock: zypper.mod_repo( name, **{"url": url, "refresh": True, "gpgautoimport": True} ) self.assertEqual( zypper.__zypper__(root=None).xml.call.call_args_list, [ call("ar", url, name), ], ) zypper.__zypper__(root=None).refreshable.xml.call.assert_called_once_with( "mr", "--refresh", name ) refreshmock.assert_called_once_with( gpgautoimport=True, refresh=True, repos=name, root=None, url="http://repo.url/some/path", ) def test_repo_noadd_mod_ref(self): """ Test mod_repo detects the repo already exists, calls modify to update 'autorefresh' and refreshes the repo with `zypper --gpg-auto-import-keys refresh <repo-name>` :return: """ url = self.new_repo_config["url"] name = self.new_repo_config["name"] self.zypper_patcher_config["_get_configured_repos"] = Mock( **{"return_value.sections.return_value": [name]} ) zypper_patcher = patch.multiple( "salt.modules.zypperpkg", **self.zypper_patcher_config ) with zypper_patcher, patch.object(zypper, "refresh_db", Mock()) as refreshmock: zypper.mod_repo( name, **{"url": url, "refresh": True, "gpgautoimport": True} ) zypper.__zypper__(root=None).refreshable.xml.call.assert_called_once_with( "mr", "--refresh", name ) refreshmock.assert_called_once_with( gpgautoimport=True, refresh=True, repos=name, root=None, url="http://repo.url/some/path", ) def test_wildcard_to_query_match_all(self): """ Test wildcard to query match all pattern :return: """ xmldoc = """<?xml version='1.0'?><stream> <search-result version="0.0"><solvable-list> <solvable status="installed" name="libzypp" kind="package" edition="16.2.4-19.5" arch="x86_64" repository="SLE-12-SP2-x86_64-Pool"/> <solvable status="not-installed" name="libzypp" kind="srcpackage" edition="16.3.2-25.1" arch="noarch" repository="SLE-12-SP2-x86_64-Update"/> <solvable status="not-installed" name="libzypp" kind="srcpackage" edition="16.5.2-27.9.1" arch="noarch" repository="SLE-12-SP2-x86_64-Update"/> <solvable status="other-version" name="libzypp" kind="package" edition="16.3.2-25.1" arch="x86_64" repository="SLE-12-SP2-x86_64-Update"/> <solvable status="other-version" name="libzypp" kind="package" edition="16.5.2-27.9.1" arch="x86_64" repository="SLE-12-SP2-x86_64-Update"/> <solvable status="installed" name="libzypp" kind="package" edition="16.2.4-19.5" arch="x86_64" repository="(System Packages)"/> </solvable-list></search-result></stream> """ _zpr = MagicMock() _zpr.nolock.xml.call = MagicMock(return_value=minidom.parseString(xmldoc)) wcard = zypper.Wildcard(_zpr) wcard.name, wcard.version = "libzypp", "*" assert wcard._get_scope_versions(wcard._get_available_versions()) == [ "16.2.4-19.5", "16.3.2-25.1", "16.5.2-27.9.1", ] def test_wildcard_to_query_multiple_asterisk(self): """ Test wildcard to query match multiple asterisk :return: """ xmldoc = """<?xml version='1.0'?><stream> <search-result version="0.0"><solvable-list> <solvable status="installed" name="libzypp" kind="package" edition="16.2.4-19.5" arch="x86_64" repository="foo"/> <solvable status="other-version" name="libzypp" kind="package" edition="16.2.5-25.1" arch="x86_64" repository="foo"/> <solvable status="other-version" name="libzypp" kind="package" edition="16.2.6-27.9.1" arch="x86_64" repository="foo"/> </solvable-list></search-result></stream> """ _zpr = MagicMock() _zpr.nolock.xml.call = MagicMock(return_value=minidom.parseString(xmldoc)) wcard = zypper.Wildcard(_zpr) wcard.name, wcard.version = "libzypp", "16.2.*-2*" assert wcard._get_scope_versions(wcard._get_available_versions()) == [ "16.2.5-25.1", "16.2.6-27.9.1", ] def test_wildcard_to_query_exact_match_at_end(self): """ Test wildcard to query match exact pattern at the end :return: """ xmldoc = """<?xml version='1.0'?><stream> <search-result version="0.0"><solvable-list> <solvable status="installed" name="libzypp" kind="package" edition="16.2.4-19.5" arch="x86_64" repository="foo"/> <solvable status="other-version" name="libzypp" kind="package" edition="16.2.5-25.1" arch="x86_64" repository="foo"/> <solvable status="other-version" name="libzypp" kind="package" edition="16.2.6-27.9.1" arch="x86_64" repository="foo"/> </solvable-list></search-result></stream> """ _zpr = MagicMock() _zpr.nolock.xml.call = MagicMock(return_value=minidom.parseString(xmldoc)) wcard = zypper.Wildcard(_zpr) wcard.name, wcard.version = "libzypp", "16.2.5*" assert wcard._get_scope_versions(wcard._get_available_versions()) == [ "16.2.5-25.1" ] def test_wildcard_to_query_exact_match_at_beginning(self): """ Test wildcard to query match exact pattern at the beginning :return: """ xmldoc = """<?xml version='1.0'?><stream> <search-result version="0.0"><solvable-list> <solvable status="installed" name="libzypp" kind="package" edition="16.2.4-19.5" arch="x86_64" repository="foo"/> <solvable status="other-version" name="libzypp" kind="package" edition="16.2.5-25.1" arch="x86_64" repository="foo"/> <solvable status="other-version" name="libzypp" kind="package" edition="17.2.6-27.9.1" arch="x86_64" repository="foo"/> </solvable-list></search-result></stream> """ _zpr = MagicMock() _zpr.nolock.xml.call = MagicMock(return_value=minidom.parseString(xmldoc)) wcard = zypper.Wildcard(_zpr) wcard.name, wcard.version = "libzypp", "*.1" assert wcard._get_scope_versions(wcard._get_available_versions()) == [ "16.2.5-25.1", "17.2.6-27.9.1", ] def test_wildcard_to_query_usage(self): """ Test wildcard to query usage. :return: """ xmldoc = """<?xml version='1.0'?><stream> <search-result version="0.0"><solvable-list> <solvable status="installed" name="libzypp" kind="package" edition="16.2.4-19.5" arch="x86_64" repository="foo"/> <solvable status="other-version" name="libzypp" kind="package" edition="16.2.5-25.1" arch="x86_64" repository="foo"/> <solvable status="other-version" name="libzypp" kind="package" edition="17.2.6-27.9.1" arch="x86_64" repository="foo"/> </solvable-list></search-result></stream> """ _zpr = MagicMock() _zpr.nolock.xml.call = MagicMock(return_value=minidom.parseString(xmldoc)) assert zypper.Wildcard(_zpr)("libzypp", "16.2.4*") == "16.2.4-19.5" assert zypper.Wildcard(_zpr)("libzypp", "16.2*") == "16.2.5-25.1" assert zypper.Wildcard(_zpr)("libzypp", "*6-*") == "17.2.6-27.9.1" assert zypper.Wildcard(_zpr)("libzypp", "*.1") == "17.2.6-27.9.1" def test_wildcard_to_query_noversion(self): """ Test wildcard to query when no version has been passed on. :return: """ xmldoc = """<?xml version='1.0'?><stream> <search-result version="0.0"><solvable-list> <solvable status="installed" name="libzypp" kind="package" edition="16.2.4-19.5" arch="x86_64" repository="foo"/> <solvable status="other-version" name="libzypp" kind="package" edition="16.2.5-25.1" arch="x86_64" repository="foo"/> <solvable status="other-version" name="libzypp" kind="package" edition="17.2.6-27.9.1" arch="x86_64" repository="foo"/> </solvable-list></search-result></stream> """ _zpr = MagicMock() _zpr.nolock.xml.call = MagicMock(return_value=minidom.parseString(xmldoc)) assert zypper.Wildcard(_zpr)("libzypp", None) is None def test_wildcard_to_query_typecheck(self): """ Test wildcard to query typecheck. :return: """ xmldoc = """<?xml version='1.0'?><stream> <search-result version="0.0"><solvable-list> <solvable status="installed" name="libzypp" kind="package" edition="16.2.4-19.5" arch="x86_64" repository="foo"/> <solvable status="other-version" name="libzypp" kind="package" edition="16.2.5-25.1" arch="x86_64" repository="foo"/> <solvable status="other-version" name="libzypp" kind="package" edition="17.2.6-27.9.1" arch="x86_64" repository="foo"/> </solvable-list></search-result></stream> """ _zpr = MagicMock() _zpr.nolock.xml.call = MagicMock(return_value=minidom.parseString(xmldoc)) assert isinstance(zypper.Wildcard(_zpr)("libzypp", "*.1"), str) def test_wildcard_to_query_condition_preservation(self): """ Test wildcard to query Zypper condition preservation. :return: """ xmldoc = """<?xml version='1.0'?><stream> <search-result version="0.0"><solvable-list> <solvable status="installed" name="libzypp" kind="package" edition="16.2.4-19.5" arch="x86_64" repository="foo"/> <solvable status="other-version" name="libzypp" kind="package" edition="16.2.5-25.1" arch="x86_64" repository="foo"/> <solvable status="other-version" name="libzypp" kind="package" edition="17.2.6-27.9.1" arch="x86_64" repository="foo"/> </solvable-list></search-result></stream> """ _zpr = MagicMock() _zpr.nolock.xml.call = MagicMock(return_value=minidom.parseString(xmldoc)) for op in zypper.Wildcard.Z_OP: assert zypper.Wildcard(_zpr)("libzypp", f"{op}*.1") == f"{op}17.2.6-27.9.1" # Auto-fix feature: moves operator from end to front for op in zypper.Wildcard.Z_OP: assert zypper.Wildcard(_zpr)("libzypp", f"16*{op}") == f"{op}16.2.5-25.1" def test_wildcard_to_query_unsupported_operators(self): """ Test wildcard to query unsupported operators. :return: """ xmldoc = """<?xml version='1.0'?><stream> <search-result version="0.0"><solvable-list> <solvable status="installed" name="libzypp" kind="package" edition="16.2.4-19.5" arch="x86_64" repository="foo"/> <solvable status="other-version" name="libzypp" kind="package" edition="16.2.5-25.1" arch="x86_64" repository="foo"/> <solvable status="other-version" name="libzypp" kind="package" edition="17.2.6-27.9.1" arch="x86_64" repository="foo"/> </solvable-list></search-result></stream> """ _zpr = MagicMock() _zpr.nolock.xml.call = MagicMock(return_value=minidom.parseString(xmldoc)) with self.assertRaises(CommandExecutionError): for op in [">>", "==", "<<", "+"]: zypper.Wildcard(_zpr)("libzypp", f"{op}*.1") @patch("salt.modules.zypperpkg._get_visible_patterns") def test__get_installed_patterns(self, get_visible_patterns): """Test installed patterns in the system""" get_visible_patterns.return_value = { "package-a": {"installed": True, "summary": "description a"}, "package-b": {"installed": False, "summary": "description b"}, } salt_mock = { "cmd.run": MagicMock( return_value="""pattern() = package-a pattern-visible() pattern() = package-c""" ), } with patch.dict("salt.modules.zypperpkg.__salt__", salt_mock): assert zypper._get_installed_patterns() == { "package-a": {"installed": True, "summary": "description a"}, "package-c": {"installed": True, "summary": "Non-visible pattern"}, } @patch("salt.modules.zypperpkg._get_visible_patterns") def test__get_installed_patterns_with_alias(self, get_visible_patterns): """Test installed patterns in the system if they have alias""" get_visible_patterns.return_value = { "package-a": {"installed": True, "summary": "description a"}, "package-b": {"installed": False, "summary": "description b"}, } salt_mock = { "cmd.run": MagicMock( return_value="""pattern() = .package-a-alias pattern() = package-a pattern-visible() pattern() = package-c""" ), } with patch.dict("salt.modules.zypperpkg.__salt__", salt_mock): assert zypper._get_installed_patterns() == { "package-a": {"installed": True, "summary": "description a"}, "package-c": {"installed": True, "summary": "Non-visible pattern"}, } @patch("salt.modules.zypperpkg._get_visible_patterns") def test_list_patterns(self, get_visible_patterns): """Test available patterns in the repo""" get_visible_patterns.return_value = { "package-a": {"installed": True, "summary": "description a"}, "package-b": {"installed": False, "summary": "description b"}, } assert zypper.list_patterns() == { "package-a": {"installed": True, "summary": "description a"}, "package-b": {"installed": False, "summary": "description b"}, } def test__clean_cache_empty(self): """Test that an empty cached can be cleaned""" context = {} with patch.dict(zypper.__context__, context): zypper._clean_cache() assert context == {} def test__clean_cache_filled(self): """Test that a filled cached can be cleaned""" context = { "pkg.list_pkgs_/mnt_[]": None, "pkg.list_pkgs_/mnt_[patterns]": None, "pkg.list_provides": None, "pkg.other_data": None, } with patch.dict(zypper.__context__, context): zypper._clean_cache() self.assertEqual(zypper.__context__, {"pkg.other_data": None}) def test_services_need_restart(self): """ Test that zypper ps is used correctly to list services that need to be restarted. """ expected = ["salt-minion", "firewalld"] zypper_output = "salt-minion\nfirewalld" zypper_mock = Mock() zypper_mock(root=None).nolock.call = Mock(return_value=zypper_output) with patch("salt.modules.zypperpkg.__zypper__", zypper_mock): assert zypper.services_need_restart() == expected zypper_mock(root=None).nolock.call.assert_called_with("ps", "-sss") def test_is_rpm_lock_no_error(self): with patch.object(os.path, "exists", return_value=True): self.assertFalse(zypper.__zypper__._is_rpm_lock()) def test_rpm_lock_does_not_exist(self): if salt.utils.files.is_fcntl_available(): zypper.__zypper__.exit_code = 1 with patch.object( os.path, "exists", return_value=False ) as mock_path_exists: self.assertFalse(zypper.__zypper__._is_rpm_lock()) mock_path_exists.assert_called_with(zypper.__zypper__.RPM_LOCK) zypper.__zypper__._reset() def test_rpm_lock_acquirable(self): if salt.utils.files.is_fcntl_available(): zypper.__zypper__.exit_code = 1 with patch.object(os.path, "exists", return_value=True), patch( "fcntl.lockf", side_effect=OSError(errno.EAGAIN, "") ) as lockf_mock, patch("salt.utils.files.fopen", mock_open()): self.assertTrue(zypper.__zypper__._is_rpm_lock()) lockf_mock.assert_called() zypper.__zypper__._reset() def test_rpm_lock_not_acquirable(self): if salt.utils.files.is_fcntl_available(): zypper.__zypper__.exit_code = 1 with patch.object(os.path, "exists", return_value=True), patch( "fcntl.lockf" ) as lockf_mock, patch("salt.utils.files.fopen", mock_open()): self.assertFalse(zypper.__zypper__._is_rpm_lock()) self.assertEqual(lockf_mock.call_count, 2) zypper.__zypper__._reset()