diff --git a/sw-patch/cgcs-patch/cgcs_patch/config.py b/sw-patch/cgcs-patch/cgcs_patch/config.py index 0a7d4d5f..0fe48d1c 100644 --- a/sw-patch/cgcs-patch/cgcs_patch/config.py +++ b/sw-patch/cgcs-patch/cgcs_patch/config.py @@ -49,7 +49,7 @@ def read_config(): global controller_port global agent_port - config = configparser.SafeConfigParser(defaults) + config = configparser.ConfigParser(defaults) config.read(patching_conf) patching_conf_mtime = os.stat(patching_conf).st_mtime @@ -105,7 +105,7 @@ def get_mgmt_iface(): # so return the cached value. return mgmt_if - config = configparser.SafeConfigParser() + config = configparser.ConfigParser() # The platform.conf file has no section headers, which causes problems # for ConfigParser. So we'll fake it out. diff --git a/sw-patch/cgcs-patch/cgcs_patch/tests/test_patch_client.py b/sw-patch/cgcs-patch/cgcs_patch/tests/test_patch_client.py new file mode 100644 index 00000000..034e86f4 --- /dev/null +++ b/sw-patch/cgcs-patch/cgcs_patch/tests/test_patch_client.py @@ -0,0 +1,195 @@ +# +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (c) 2019-2022 Wind River Systems, Inc. +# + +import json +import mock +import os +import sys +import testtools + +from cgcs_patch import patch_client + + +FAKE_SW_VERSION = "1.2.3" +PATCH_FLAG_NO = "N" +PATCH_FLAG_YES = "Y" +STATE_APPLIED = "Applied" +STATE_AVAILABLE = "Available" +STATE_NA = "n/a" +STATUS_DEV = "DEV" + +FAKE_PATCH_ID_1 = "PATCH_1" +FAKE_PATCH_1_META = { + "apply_active_release_only": "", + "description": "Patch 1 description", + "install_instructions": "Patch 1 instructions", + "patchstate": STATE_NA, + "reboot_required": PATCH_FLAG_YES, + "repostate": STATE_APPLIED, + "requires": [], + "status": STATUS_DEV, + "summary": "Patch 1 summary", + "sw_version": FAKE_SW_VERSION, + "unremovable": PATCH_FLAG_NO, + "warnings": "Patch 1 warnings", +} + +FAKE_PATCH_ID_2 = "PATCH_2" +FAKE_PATCH_2_META = { + "apply_active_release_only": "", + "description": "Patch 2 description", + "install_instructions": "Patch 2 instructions", + "patchstate": STATE_AVAILABLE, + "reboot_required": PATCH_FLAG_NO, + "repostate": STATE_AVAILABLE, + "requires": [FAKE_PATCH_ID_1], + "status": STATUS_DEV, + "summary": "Patch 2 summary", + "sw_version": FAKE_SW_VERSION, + "unremovable": PATCH_FLAG_NO, + "warnings": "Patch 2 warnings", +} + + +class FakeResponse(object): + """This is used to mock a requests.get result""" + def __init__(self, json_data, status_code): + self.json_data = json_data + self.status_code = status_code + self.text = json.dumps(json_data) + + def json(self): + return self.json_data + + +class PatchClientTestCase(testtools.TestCase): + PROG = "sw-patch" + + MOCK_ENV = { + 'OS_AUTH_URL': 'FAKE_OS_AUTH_URL', + 'OS_PROJECT_NAME': 'FAKE_OS_PROJECT_NAME', + 'OS_PROJECT_DOMAIN_NAME': 'FAKE_OS_PROJECT_DOMAIN_NAME', + 'OS_USERNAME': 'FAKE_OS_USERNAME', + 'OS_PASSWORD': 'FAKE_OS_PASSWORD', + 'OS_USER_DOMAIN_NAME': 'FAKE_OS_USER_DOMAIN_NAME', + 'OS_REGION_NAME': 'FAKE_OS_REGION_NAME', + 'OS_INTERFACE': 'FAKE_OS_INTERFACE' + } + + # mock_map is populated by the setUp method + mock_map = {} + + def setUp(self): + super(PatchClientTestCase, self).setUp() + + def _mock_requests_get(*args, **kwargs): + key = args[0] + _ = kwargs # kwargs is unused + # if the key is not found in the mock_map + # we return a 404 (not found) + return self.mock_map.get(key, + FakeResponse(None, 404)) + + patcher = mock.patch( + 'requests.get', + side_effect=_mock_requests_get) + self.mock_requests_get = patcher.start() + self.addCleanup(patcher.stop) + + +class PatchClientHelpTestCase(PatchClientTestCase): + """Test the sw-patch CLI calls that invoke 'help' + + 'check_for_os_region_name' is mocked to help determine + which code path is used since many code paths can short + circuit and invoke 'help' in failure cases. + """ + + def _test_print_help(self, shell_args=None): + with mock.patch.dict(os.environ, self.MOCK_ENV): + with mock.patch.object(sys, 'argv', + shell_args): + # mock 'print' so running unit tests will + # not print help usage to the tox output + with mock.patch('builtins.print'): + # Every client invocation invokes exit + # which raises SystemExit + self.assertRaises(SystemExit, + patch_client.main) + + @mock.patch('cgcs_patch.patch_client.check_for_os_region_name') + def test_main_no_args_calls_help(self, mock_check): + """When no arguments are called, this should invoke print_help""" + shell_args = [self.PROG, ] + self._test_print_help(shell_args=shell_args) + mock_check.assert_not_called() + + @mock.patch('cgcs_patch.patch_client.check_for_os_region_name') + def test_main_help(self, mock_check): + """When no arguments are called, this should invoke print_help""" + shell_args = [self.PROG, "--help"] + self._test_print_help(shell_args=shell_args) + mock_check.assert_called() + + @mock.patch('cgcs_patch.patch_client.check_for_os_region_name') + def test_main_invalid_action_calls_help(self, mock_check): + """invalid args should invoke print_help""" + shell_args = [self.PROG, "invalid_arg"] + self._test_print_help(shell_args=shell_args) + mock_check.assert_called() + + +class PatchClientQueryTestCase(PatchClientTestCase): + """Test the sw-patch CLI calls that invoke 'query'""" + + TEST_URL_ALL = "http://127.0.0.1:5487/patch/query?show=all" + TEST_PATCH_DATA_SHOW_ALL = { + "pd": { + FAKE_PATCH_ID_1: FAKE_PATCH_1_META, + FAKE_PATCH_ID_2: FAKE_PATCH_2_META, + } + } + + TEST_URL_APPLIED = "http://127.0.0.1:5487/patch/query?show=applied" + TEST_PATCH_DATA_SHOW_APPLIED = { + "pd": { + FAKE_PATCH_ID_1: FAKE_PATCH_1_META, + } + } + + def setUp(self): + super(PatchClientQueryTestCase, self).setUp() + # update the mock_map with a query result + self.mock_map[self.TEST_URL_ALL] = FakeResponse( + self.TEST_PATCH_DATA_SHOW_ALL, 200) + self.mock_map[self.TEST_URL_APPLIED] = FakeResponse( + self.TEST_PATCH_DATA_SHOW_APPLIED, 200) + + def _test_query(self, shell_args=None): + with mock.patch.dict(os.environ, self.MOCK_ENV): + with mock.patch.object(sys, 'argv', + shell_args): + # mock 'print' so running unit tests will + # not print to the tox output + with mock.patch('builtins.print'): + # Every client invocation invokes exit + # which raises SystemExit + self.assertRaises(SystemExit, + patch_client.main) + + def test_query(self): + shell_args = [self.PROG, "query"] + self._test_query(shell_args=shell_args) + self.mock_requests_get.assert_called_with( + self.TEST_URL_ALL, + headers=mock.ANY) + + def test_query_patch(self): + shell_args = [self.PROG, "query", "applied"] + self._test_query(shell_args=shell_args) + self.mock_requests_get.assert_called_with( + self.TEST_URL_APPLIED, + headers=mock.ANY) diff --git a/sw-patch/cgcs-patch/cgcs_patch/tests/test_patch_utils.py b/sw-patch/cgcs-patch/cgcs_patch/tests/test_patch_utils.py index 393e3dc8..66c73897 100644 --- a/sw-patch/cgcs-patch/cgcs_patch/tests/test_patch_utils.py +++ b/sw-patch/cgcs-patch/cgcs_patch/tests/test_patch_utils.py @@ -25,12 +25,10 @@ class CgcsPatchUtilsTestCase(testtools.TestCase): def test_gethostbyname(self): result = cgcs_patch.utils.gethostbyname('localhost') - print("gethostbyname returned %s for localhost" % result) self.assertIn(result, ['127.0.0.1', '::1']) def test_gethostbyname_failure(self): result = cgcs_patch.utils.gethostbyname('xfakehostx') - print("gethostbyname returned %s for xfakehostx" % result) self.assertIsNone(result) @mock.patch('cgcs_patch.utils.gethostbyname') diff --git a/sw-patch/cgcs-patch/pylint.rc b/sw-patch/cgcs-patch/pylint.rc index 19e58ab3..4b89719e 100644 --- a/sw-patch/cgcs-patch/pylint.rc +++ b/sw-patch/cgcs-patch/pylint.rc @@ -36,87 +36,6 @@ extension-pkg-whitelist=lxml # either give multiple identifier separated by comma (,) or put this option # multiple time. See also the "--disable" option for examples. # -# Python3 checker: -# -# E1601: print-statement -# E1602: parameter-unpacking -# E1603: unpacking-in-except -# E1604: old-raise-syntax -# E1605: backtick -# E1606: long-suffix -# E1607: old-ne-operator -# E1608: old-octal-literal -# E1609: import-star-module-level -# E1610: non-ascii-bytes-literal -# E1611: invalid-unicode-literal -# W1601: apply-builtin -# W1602: basestring-builtin -# W1603: buffer-builtin -# W1604: cmp-builtin -# W1605: coerce-builtin -# W1606: execfile-builtin -# W1607: file-builtin -# W1608: long-builtin -# W1609: raw_input-builtin -# W1610: reduce-builtin -# W1611: standarderror-builtin -# W1612: unicode-builtin -# W1613: xrange-builtin -# W1614: coerce-method -# W1615: delslice-method -# W1616: getslice-method -# W1617: setslice-method -# W1618: no-absolute-import -# W1619: old-division -# W1620: dict-iter-method -# W1621: dict-view-method -# W1622: next-method-called -# W1623: metaclass-assignment -# W1624: indexing-exception -# W1625: raising-string -# W1626: reload-builtin -# W1627: oct-method -# W1628: hex-method -# W1629: nonzero-method -# W1630: cmp-method -# W1632: input-builtin -# W1633: round-builtin -# W1634: intern-builtin -# W1635: unichr-builtin -# W1636: map-builtin-not-iterating -# W1637: zip-builtin-not-iterating -# W1638: range-builtin-not-iterating -# W1639: filter-builtin-not-iterating -# W1640: using-cmp-argument -# W1641: eq-without-hash -# W1642: div-method -# W1643: idiv-method -# W1644: rdiv-method -# W1645: exception-message-attribute -# W1646: invalid-str-codec -# W1647: sys-max-int -# W1648: bad-python3-import -# W1649: deprecated-string-function -# W1650: deprecated-str-translate-call -# W1651: deprecated-itertools-function -# W1652: deprecated-types-field -# W1653: next-method-defined -# W1654: dict-items-not-iterating -# W1655: dict-keys-not-iterating -# W1656: dict-values-not-iterating -# W1657: deprecated-operator-function -# W1658: deprecated-urllib-function -# W1659: xreadlines-attribute -# W1660: deprecated-sys-function -# W1661: exception-escape -# W1662: comprehension-escape -enable=E1603,E1609,E1610,E1602,E1606,E1608,E1607,E1605,E1604,E1601,E1611,W1652, - W1651,W1649,W1657,W1660,W1658,W1659,W1623,W1622,W1620,W1621,W1645,W1641, - W1624,W1648,W1625,W1611,W1662,W1661,W1650,W1640,W1630,W1614,W1615,W1642, - W1616,W1628,W1643,W1629,W1627,W1644,W1617,W1601,W1602,W1603,W1604,W1605, - W1654,W1655,W1656,W1619,W1606,W1607,W1639,W1618,W1632,W1634,W1608,W1636, - W1653,W1646,W1638,W1609,W1610,W1626,W1633,W1647,W1635,W1612,W1613,W1637 - # Disable the message, report, category or checker with the given id(s). You # can either give multiple identifiers separated by comma (,) or put this # option multiple times (only on the command line, not in the configuration @@ -126,6 +45,43 @@ enable=E1603,E1609,E1610,E1602,E1606,E1608,E1607,E1605,E1604,E1601,E1611,W1652, # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" +# -Conventions- +# C0103 invalid-name +# C0114 missing-module-docstring +# C0115 missing-class-docstring +# C0116 missing-function-docstring +# C0201 consider-iterating-dictionary +# C0206 consider-using-dict-items +# C0209 consider-using-f-string +# C2801 unnecessary-dunder-call +# C0301 line-too-long +# C0302 too-many-lines +# C0325 superfluous-parens +# C0411 wrong-import-order +# C0415 import-outside-toplevel +# -Refactoring- +# R0205 useless-object-inheritance +# R0402 consider-using-from-import +# R0801 Similar lines in x files +# R0902 too-many-instance-attributes +# R0903 too-few-public-methods +# R0904 too-many-public-methods +# R0911 too-many-return-statements +# R0912 too-many-branches +# R0913 too-many-arguments +# R0914 too-many-locals +# R0915 too-many-statements +# R1702 too-many-nested-blocks +# R1705 no-else-return +# R1710 inconsistent-return-statements +# R1714 consider-using-in +# R1715 consider-using-get +# R1722 consider-using-sys-exit +# R1724 no-else-continue +# R1725 super-with-arguments +# R1732 consider-using-with +# R1735 use-dict-literal +# -Warnings- # W0107 unnecessary-pass # W0602 global-variable-not-assigned # W0603 global-statement @@ -133,7 +89,12 @@ enable=E1603,E1609,E1610,E1602,E1606,E1608,E1607,E1605,E1604,E1601,E1611,W1652, # W0707 raise-missing-from # W1505 deprecated-method # W1514 unspecified-encoding -disable=C,R,W0107,W0602,W0603,W0703,W0707,W1505,W1514 +disable= C0103,C0114,C0115,C0116,C0201,C0206,C0209,C2801, + C0301,C0302,C0325,C0411,C0415, + R0205,R0402,R0801,R0902,R0903,R0904,R0911, + R0912,R0913,R0914,R0915,R1702,R1705,R1710,R1714, + R1715,R1722,R1724,R1725,R1732,R1735, + W0107,W0602,W0603,W0703,W0707,W1505,W1514 [REPORTS] diff --git a/sw-patch/cgcs-patch/tox.ini b/sw-patch/cgcs-patch/tox.ini index e7e318f7..ba6da5af 100644 --- a/sw-patch/cgcs-patch/tox.ini +++ b/sw-patch/cgcs-patch/tox.ini @@ -38,7 +38,7 @@ setenv = VIRTUAL_ENV={envdir} OS_TEST_TIMEOUT=60 PYTHONDONTWRITEBYTECODE=1 PYTHONHASHSEED=0 - PYTHONWARNINGS=default::DeprecationWarning + PYTHONWARNINGS=ignore::DeprecationWarning,ignore::SyntaxWarning PIP_DISABLE_PIP_VERSION_CHECK=1 sitepackages = False @@ -98,7 +98,7 @@ ignore = H306,H401,H404,H405,W504,E501 exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build,release-tag-* # H106: Don't put vim configuration in source files (off by default). # H203: Use assertIs(Not)None to check for None (off by default). -# (todo) enable H904 Delay string interpolations at logging calls (off by default) +# H904 Delay string interpolations at logging calls (off by default) enable-extensions = H106 H203,H904 max-line-length = 80 @@ -113,6 +113,7 @@ commands = pylint cgcs_patch --rcfile=./pylint.rc [testenv:cover] setenv = + PYTHONWARNINGS=ignore::DeprecationWarning,ignore::SyntaxWarning PYTHON=coverage run --parallel-mode PYTHONDONTWRITEBYTECODE=True