From c5be3ca4c450a83d4e6e11c71da6f767d2a1bd17 Mon Sep 17 00:00:00 2001 From: Joe LeVeque Date: Mon, 29 Mar 2021 10:37:12 -0700 Subject: [PATCH] [psud] Increase unit test coverage; Refactor mock platform (#154) - Refactor psud such that the `run()` method does not contain an infinite loop, thus allowing us to unit test it - Refactor mock_platform.py such that it inherits from sonic-platform-common in order to ensure it is aligned with the current API definitions (this introduces a test-time dependency on the sonic-platform-common package) - Eliminate the need to check for a `PSUD_UNIT_TESTING` environment variable in daemon code - Increase overall unit test unit test coverage from 78% to 97% --- sonic-psud/pytest.ini | 2 +- sonic-psud/scripts/psud | 146 +++--- sonic-psud/setup.py | 3 +- sonic-psud/tests/mock_device_base.py | 11 - sonic-psud/tests/mock_platform.py | 421 +++++++++++++----- .../mocked_libs/sonic_platform/__init__.py | 5 + .../tests/mocked_libs/sonic_platform/psu.py | 10 + .../tests/mocked_libs/swsscommon/__init__.py | 5 + .../swsscommon/swsscommon.py} | 7 + sonic-psud/tests/test_DaemonPsud.py | 243 ++++++---- sonic-psud/tests/test_PsuChassisInfo.py | 91 ++-- sonic-psud/tests/test_PsuStatus.py | 26 +- sonic-psud/tests/test_psud.py | 36 +- 13 files changed, 663 insertions(+), 343 deletions(-) delete mode 100644 sonic-psud/tests/mock_device_base.py create mode 100644 sonic-psud/tests/mocked_libs/sonic_platform/__init__.py create mode 100644 sonic-psud/tests/mocked_libs/sonic_platform/psu.py create mode 100644 sonic-psud/tests/mocked_libs/swsscommon/__init__.py rename sonic-psud/tests/{mock_swsscommon.py => mocked_libs/swsscommon/swsscommon.py} (89%) diff --git a/sonic-psud/pytest.ini b/sonic-psud/pytest.ini index aa4fe636e352..d90ee9ed9e12 100644 --- a/sonic-psud/pytest.ini +++ b/sonic-psud/pytest.ini @@ -1,2 +1,2 @@ [pytest] -addopts = --cov=scripts --cov-report html --cov-report term --cov-report xml --junitxml=test-results.xml -v +addopts = --cov=scripts --cov-report html --cov-report term --cov-report xml --junitxml=test-results.xml -vv diff --git a/sonic-psud/scripts/psud b/sonic-psud/scripts/psud index bb7ed567ff03..d33ad1861bc5 100644 --- a/sonic-psud/scripts/psud +++ b/sonic-psud/scripts/psud @@ -9,21 +9,14 @@ The loop interval is PSU_INFO_UPDATE_PERIOD_SECS in seconds. """ -import os import signal import sys import threading from datetime import datetime +from sonic_platform.psu import Psu from sonic_py_common import daemon_base, logger - -# If unit testing is occurring, mock swsscommon and module_base -if os.getenv("PSUD_UNIT_TESTING") == "1": - from tests.mock_platform import MockPsu as Psu - from tests import mock_swsscommon as swsscommon -else: - from sonic_platform.psu import Psu - from swsscommon import swsscommon +from swsscommon import swsscommon # @@ -32,8 +25,8 @@ else: # TODO: Once we no longer support Python 2, we can eliminate this and get the # name using the 'name' field (e.g., `signal.SIGINT.name`) starting with Python 3.5 -SIGNALS_TO_NAMES_DICT = dict((getattr(signal, n), n) \ - for n in dir(signal) if n.startswith('SIG') and '_' not in n ) +SIGNALS_TO_NAMES_DICT = dict((getattr(signal, n), n) + for n in dir(signal) if n.startswith('SIG') and '_' not in n) SYSLOG_IDENTIFIER = "psud" @@ -85,8 +78,11 @@ PSUUTIL_LOAD_ERROR = 1 platform_psuutil = None platform_chassis = None +exit_code = 0 # temporary wrappers that are compliable with both new platform api and old-style plugin mode + + def _wrapper_get_num_psus(): if platform_chassis is not None: try: @@ -131,7 +127,7 @@ def psu_db_update(psu_tbl, psu_num): psu_tbl.set(get_psu_key(psu_index), fvs) -# try get information from platform API and return a default value if caught NotImplementedError +# try get information from platform API and return a default value if we catch NotImplementedError def try_get(callback, default=None): """ Handy function to invoke the callback and catch NotImplementedError @@ -257,11 +253,10 @@ class PsuChassisInfo(logger.Logger): self.master_status_good = master_status_good # Update the PSU master status LED - try: - color = Psu.STATUS_LED_COLOR_GREEN if master_status_good else Psu.STATUS_LED_COLOR_RED - Psu.set_status_master_led(color) - except NotImplementedError: - self.log_warning("set_status_master_led() not implemented") + # set_status_master_led() is a class method implemented in PsuBase + # so we do not need to catch NotImplementedError here + color = Psu.STATUS_LED_COLOR_GREEN if master_status_good else Psu.STATUS_LED_COLOR_RED + Psu.set_status_master_led(color) log_on_status_changed(self, self.master_status_good, 'PSU supplied power warning cleared: supplied power is back to normal.', @@ -351,32 +346,21 @@ class DaemonPsud(daemon_base.DaemonBase): def __init__(self, log_identifier): super(DaemonPsud, self).__init__(log_identifier) - self.stop = threading.Event() + # Set minimum logging level to INFO + self.set_min_log_priority_info() + + self.stop_event = threading.Event() + self.num_psus = 0 self.psu_status_dict = {} + self.chassis_tbl = None self.fan_tbl = None + self.psu_tbl = None self.psu_chassis_info = None self.first_run = True - # Signal handler - def signal_handler(self, sig, frame): - if sig == signal.SIGHUP: - self.log_info("Caught SIGHUP - ignoring...") - elif sig == signal.SIGINT: - self.log_info("Caught SIGINT - exiting...") - self.stop.set() - elif sig == signal.SIGTERM: - self.log_info("Caught SIGTERM - exiting...") - self.stop.set() - else: - self.log_warning("Caught unhandled signal '{}'".format(SIGNALS_TO_NAMES_DICT[sig])) - - # Run daemon - def run(self): global platform_psuutil global platform_chassis - self.log_info("Starting up...") - # Load new platform api class try: import sonic_platform.platform @@ -394,52 +378,70 @@ class DaemonPsud(daemon_base.DaemonBase): # Connect to STATE_DB and create psu/chassis info tables state_db = daemon_base.db_connect("STATE_DB") - chassis_tbl = swsscommon.Table(state_db, CHASSIS_INFO_TABLE) - psu_tbl = swsscommon.Table(state_db, PSU_INFO_TABLE) + self.chassis_tbl = swsscommon.Table(state_db, CHASSIS_INFO_TABLE) + self.psu_tbl = swsscommon.Table(state_db, PSU_INFO_TABLE) self.fan_tbl = swsscommon.Table(state_db, FAN_INFO_TABLE) self.phy_entity_tbl = swsscommon.Table(state_db, PHYSICAL_ENTITY_INFO_TABLE) # Post psu number info to STATE_DB - psu_num = _wrapper_get_num_psus() - fvs = swsscommon.FieldValuePairs([(CHASSIS_INFO_PSU_NUM_FIELD, str(psu_num))]) - chassis_tbl.set(CHASSIS_INFO_KEY, fvs) + self.num_psus = _wrapper_get_num_psus() + fvs = swsscommon.FieldValuePairs([(CHASSIS_INFO_PSU_NUM_FIELD, str(self.num_psus))]) + self.chassis_tbl.set(CHASSIS_INFO_KEY, fvs) - # Start main loop - self.log_info("Start daemon main loop") + def __del__(self): + # Delete all the information from DB and then exit + for psu_index in range(1, self.num_psus + 1): + self.psu_tbl._del(get_psu_key(psu_index)) - while not self.stop.wait(PSU_INFO_UPDATE_PERIOD_SECS): - self._update_psu_entity_info() - psu_db_update(psu_tbl, psu_num) - self.update_psu_data(psu_tbl) - self._update_led_color(psu_tbl) + self.chassis_tbl._del(CHASSIS_INFO_KEY) + self.chassis_tbl._del(CHASSIS_INFO_POWER_KEY_TEMPLATE.format(1)) - if platform_chassis and platform_chassis.is_modular_chassis(): - self.update_psu_chassis_info(chassis_tbl) + # Override signal handler from DaemonBase + def signal_handler(self, sig, frame): + FATAL_SIGNALS = [signal.SIGINT, signal.SIGTERM] + NONFATAL_SIGNALS = [signal.SIGHUP] - self.first_run = False + global exit_code - self.log_info("Stop daemon main loop") + if sig in FATAL_SIGNALS: + self.log_info("Caught signal '{}' - exiting...".format(SIGNALS_TO_NAMES_DICT[sig])) + exit_code = 128 + sig # Make sure we exit with a non-zero code so that supervisor will try to restart us + self.stop_event.set() + elif sig in NONFATAL_SIGNALS: + self.log_info("Caught signal '{}' - ignoring...".format(SIGNALS_TO_NAMES_DICT[sig])) + else: + self.log_warning("Caught unhandled signal '{}' - ignoring...".format(SIGNALS_TO_NAMES_DICT[sig])) - # Delete all the information from DB and then exit - for psu_index in range(1, psu_num + 1): - psu_tbl._del(get_psu_key(psu_index)) + # Main daemon logic + def run(self): + if self.stop_event.wait(PSU_INFO_UPDATE_PERIOD_SECS): + # We received a fatal signal + return False - chassis_tbl._del(CHASSIS_INFO_KEY) - chassis_tbl._del(CHASSIS_INFO_POWER_KEY_TEMPLATE.format(1)) + self._update_psu_entity_info() + psu_db_update(self.psu_tbl, self.num_psus) + self.update_psu_data() + self._update_led_color() - self.log_info("Shutting down...") + if platform_chassis and platform_chassis.is_modular_chassis(): + self.update_psu_chassis_info() - def update_psu_data(self, psu_tbl): + if self.first_run: + self.first_run = False + + return True + + def update_psu_data(self): if not platform_chassis: return for index, psu in enumerate(platform_chassis.get_all_psus()): try: - self._update_single_psu_data(index + 1, psu, psu_tbl) + self._update_single_psu_data(index + 1, psu) except Exception as e: self.log_warning("Failed to update PSU data - {}".format(e)) - def _update_single_psu_data(self, index, psu, psu_tbl): + def _update_single_psu_data(self, index, psu): name = get_psu_key(index) presence = _wrapper_get_psu_presence(index) power_good = False @@ -517,7 +519,7 @@ class DaemonPsud(daemon_base.DaemonBase): (PSU_INFO_POWER_FIELD, str(power)), (PSU_INFO_FRU_FIELD, str(is_replaceable)), ]) - psu_tbl.set(name, fvs) + self.psu_tbl.set(name, fvs) def _update_psu_entity_info(self): if not platform_chassis: @@ -567,15 +569,15 @@ class DaemonPsud(daemon_base.DaemonBase): except NotImplementedError: self.log_warning("set_status_led() not implemented") - def _update_led_color(self, psu_tbl): + def _update_led_color(self): if not platform_chassis: return for index, psu_status in self.psu_status_dict.items(): fvs = swsscommon.FieldValuePairs([ ('led_status', str(try_get(psu_status.psu.get_status_led, NOT_AVAILABLE))) - ]) - psu_tbl.set(get_psu_key(index), fvs) + ]) + self.psu_tbl.set(get_psu_key(index), fvs) self._update_psu_fan_led_status(psu_status.psu, index) def _update_psu_fan_led_status(self, psu, psu_index): @@ -588,14 +590,14 @@ class DaemonPsud(daemon_base.DaemonBase): ]) self.fan_tbl.set(fan_name, fvs) - def update_psu_chassis_info(self, chassis_tbl): + def update_psu_chassis_info(self): if not platform_chassis: return if not self.psu_chassis_info: self.psu_chassis_info = PsuChassisInfo(SYSLOG_IDENTIFIER, platform_chassis) - self.psu_chassis_info.run_power_budget(chassis_tbl) + self.psu_chassis_info.run_power_budget(self.chassis_tbl) self.psu_chassis_info.update_master_status() @@ -606,8 +608,16 @@ class DaemonPsud(daemon_base.DaemonBase): def main(): psud = DaemonPsud(SYSLOG_IDENTIFIER) - psud.run() + + psud.log_info("Starting up...") + + while psud.run(): + pass + + psud.log_info("Shutting down...") + + return exit_code if __name__ == '__main__': - main() + sys.exit(main()) diff --git a/sonic-psud/setup.py b/sonic-psud/setup.py index 7329681239b2..236607bac0cd 100644 --- a/sonic-psud/setup.py +++ b/sonic-psud/setup.py @@ -23,7 +23,8 @@ tests_require=[ 'mock>=2.0.0; python_version < "3.3"', 'pytest', - 'pytest-cov' + 'pytest-cov', + 'sonic_platform_common' ], classifiers=[ 'Development Status :: 4 - Beta', diff --git a/sonic-psud/tests/mock_device_base.py b/sonic-psud/tests/mock_device_base.py deleted file mode 100644 index 2aa9b9349822..000000000000 --- a/sonic-psud/tests/mock_device_base.py +++ /dev/null @@ -1,11 +0,0 @@ -class DeviceBase(): - # Device-types - DEVICE_TYPE_PSU = "PSU" - DEVICE_TYPE_FAN = "FAN" - DEVICE_TYPE_FANDRAWER = "FAN-DRAWER" - - # LED colors - STATUS_LED_COLOR_GREEN = "green" - STATUS_LED_COLOR_AMBER = "amber" - STATUS_LED_COLOR_RED = "red" - STATUS_LED_COLOR_OFF = "off" diff --git a/sonic-psud/tests/mock_platform.py b/sonic-psud/tests/mock_platform.py index d1530d666a66..6a0bb35174cc 100644 --- a/sonic-psud/tests/mock_platform.py +++ b/sonic-psud/tests/mock_platform.py @@ -1,165 +1,378 @@ -from .mock_device_base import DeviceBase +from sonic_platform_base import chassis_base +from sonic_platform_base import fan_base +from sonic_platform_base import fan_drawer_base +from sonic_platform_base import module_base +from sonic_platform_base import psu_base + + +class MockChassis(chassis_base.ChassisBase): + def __init__(self, + name='Fixed Chassis', + position_in_parent=0, + presence=True, + model='Module Model', + serial='Module Serial', + status=True): + super(MockChassis, self).__init__() + self._name = name + self._presence = presence + self._model = model + self._serial = serial + self._status = status + self._position_in_parent = position_in_parent + + self._psu_list = [] + self._fan_drawer_list = [] + self._module_list = [] + def get_num_psus(self): + return len(self._psu_list) -class MockDevice: - STATUS_LED_COLOR_GREEN = "green" - STATUS_LED_COLOR_AMBER = "amber" - STATUS_LED_COLOR_RED = "red" - STATUS_LED_COLOR_OFF = "off" + def get_all_psus(self): + return self._psu_list - def __init__(self): - self.name = None - self.presence = True - self.model = 'Module Model' - self.serial = 'Module Serial' + def get_psu(self, index): + return self._psu_list[index] - def get_name(self): - return self.name + def get_num_fan_drawers(self): + return len(self._fan_drawer_list) - def set_presence(self, presence): - self.presence = presence + def get_all_fan_drawers(self): + return self._fan_drawer_list + + def get_num_modules(self): + return len(self._module_list) + + def get_all_modules(self): + return self._module_list + + def get_status_led(self): + return self._status_led_color + + def set_status_led(self, color): + self._status_led_color = color + return True + + # Methods inherited from DeviceBase class and related setters + def get_name(self): + return self._name def get_presence(self): - return self.presence + return self._presence + + def set_presence(self, presence): + self._presence = presence def get_model(self): - return self.model + return self._model def get_serial(self): - return self.serial + return self._serial + def get_status(self): + return self._status -class MockPsu(MockDevice): - master_led_color = MockDevice.STATUS_LED_COLOR_OFF + def set_status(self, status): + self._status = status - def __init__(self, presence, status, name, position_in_parent): - self.name = name - self.presence = True - self.status = status - self.status_led_color = self.STATUS_LED_COLOR_OFF - self.position_in_parent = position_in_parent - self._fan_list = [] + def get_position_in_parent(self): + return self._position_in_parent + + def is_replaceable(self): + return self._replaceable + + +class MockFan(fan_base.FanBase): + def __init__(self, + name, + position_in_parent, + presence=True, + model='Module Model', + serial='Module Serial', + status=True, + direction=fan_base.FanBase.FAN_DIRECTION_INTAKE, + speed=50): + super(MockFan, self).__init__() + self._name = name + self._presence = presence + self._model = model + self._serial = serial + self._status = status + self._position_in_parent = position_in_parent + + self._direction = direction + self._speed = speed + self._status_led_color = self.STATUS_LED_COLOR_OFF - def get_all_fans(self): - return self._fan_list + def get_direction(self): + return self._direction - def get_powergood_status(self): - return self.status + def get_speed(self): + return self._speed + + def get_status_led(self): + return self._status_led_color def set_status_led(self, color): - self.status_led_color = color + self._status_led_color = color return True - def get_status_led(self): - return self.status_led_color + # Methods inherited from DeviceBase class and related setters + def get_name(self): + return self._name + + def get_presence(self): + return self._presence + + def set_presence(self, presence): + self._presence = presence + + def get_model(self): + return self._model + + def get_serial(self): + return self._serial + + def get_status(self): + return self._status def set_status(self, status): - self.status = status + self._status = status def get_position_in_parent(self): - return self.position_in_parent + return self._position_in_parent + + def is_replaceable(self): + return self._replaceable + + +class MockFanDrawer(fan_drawer_base.FanDrawerBase): + def __init__(self, + name, + position_in_parent, + presence=True, + model='Module Model', + serial='Module Serial', + status=True): + super(MockFanDrawer, self).__init__() + self._name = name + self._presence = presence + self._model = model + self._serial = serial + self._status = status + self._position_in_parent = position_in_parent + + self._max_consumed_power = 500.0 + self._status_led_color = self.STATUS_LED_COLOR_OFF - def set_maximum_supplied_power(self, supplied_power): - self.max_supplied_power = supplied_power + def get_status(self): + return self._status - def get_maximum_supplied_power(self): - return self.max_supplied_power + def set_status(self, status): + self._status = status - @classmethod - def set_status_master_led(cls, color): - cls.master_led_color = color + def get_maximum_consumed_power(self): + return self._max_consumed_power - @classmethod - def get_status_master_led(cls): - return cls.master_led_color + def set_maximum_consumed_power(self, consumed_power): + self._max_consumed_power = consumed_power + def get_status_led(self): + return self._status_led_color + + def set_status_led(self, color): + self._status_led_color = color + return True -class MockFanDrawer(MockDevice): - def __init__(self, fan_drawer_presence, fan_drawer_status, fan_drawer_name): - self.name = fan_drawer_name - self.presence = True - self.fan_drawer_status = fan_drawer_status + # Methods inherited from DeviceBase class and related setters + def get_name(self): + return self._name + + def get_presence(self): + return self._presence + + def set_presence(self, presence): + self._presence = presence + + def get_model(self): + return self._model + + def get_serial(self): + return self._serial def get_status(self): - return self.fan_drawer_status + return self._status def set_status(self, status): - self.fan_drawer_status = status + self._status = status + + def get_position_in_parent(self): + return self._position_in_parent + + def is_replaceable(self): + return self._replaceable + + +class MockModule(module_base.ModuleBase): + def __init__(self, + name, + position_in_parent, + presence=True, + model='Module Model', + serial='Module Serial', + status=True): + super(MockModule, self).__init__() + self._name = name + self._presence = presence + self._model = model + self._serial = serial + self._status = status + self._position_in_parent = position_in_parent + + self._max_consumed_power = 500.0 def set_maximum_consumed_power(self, consumed_power): - self.max_consumed_power = consumed_power + self._max_consumed_power = consumed_power def get_maximum_consumed_power(self): - return self.max_consumed_power + return self._max_consumed_power + # Methods inherited from DeviceBase class and related setters + def get_name(self): + return self._name -class MockFan(MockDevice): - FAN_DIRECTION_INTAKE = "intake" - FAN_DIRECTION_EXHAUST = "exhaust" + def get_presence(self): + return self._presence - def __init__(self, name, direction, speed=50): - self.name = name - self.direction = direction - self.speed = speed - self.status_led_color = self.STATUS_LED_COLOR_OFF + def set_presence(self, presence): + self._presence = presence - def get_direction(self): - return self.direction + def get_model(self): + return self._model - def get_speed(self): - return self.speed + def get_serial(self): + return self._serial - def set_status_led(self, color): - self.status_led_color = color - return True + def get_status(self): + return self._status - def get_status_led(self): - return self.status_led_color + def set_status(self, status): + self._status = status + def get_position_in_parent(self): + return self._position_in_parent + + def is_replaceable(self): + return self._replaceable + + +class MockPsu(psu_base.PsuBase): + def __init__(self, + name, + position_in_parent, + presence=True, + model='Module Model', + serial='Module Serial', + status=True, + voltage=12.0, + current=8.0, + power=100.0, + temp=30.00, + temp_high_th=50.0, + voltage_low_th=11.0, + voltage_high_th=13.0, + replaceable=True): + super(MockPsu, self).__init__() + self._name = name + self._presence = presence + self._model = model + self._serial = serial + self._status = status + self._position_in_parent = position_in_parent + self._replaceable = replaceable + + self._voltage = voltage + self._current = current + self._power = power + self._temp = temp + self._temp_high_th = temp_high_th + self._voltage_low_th = voltage_low_th + self._voltage_high_th = voltage_high_th + self._status_led_color = self.STATUS_LED_COLOR_OFF + + def get_voltage(self): + return self._voltage + + def set_voltage(self, voltage): + self._voltage = voltage + + def get_current(self): + return self._current + + def set_current(self, current): + self._current = current + + def get_power(self): + return self._power + + def set_power(self, power): + self._power = power + def get_powergood_status(self): + return self._status -class MockModule(MockDevice): - def __init__(self, module_presence, module_status, module_name): - self.name = module_name - self.presence = True - self.module_status = module_status + def get_temperature(self): + return self._temp - def get_status(self): - return self.module_status + def set_temperature(self, power): + self._temp = temp - def set_status(self, status): - self.module_status = status + def get_temperature_high_threshold(self): + return self._temp_high_th - def set_maximum_consumed_power(self, consumed_power): - self.max_consumed_power = consumed_power + def get_voltage_high_threshold(self): + return self._voltage_high_th - def get_maximum_consumed_power(self): - return self.max_consumed_power + def get_voltage_low_threshold(self): + return self._voltage_low_th + def get_maximum_supplied_power(self): + return self._max_supplied_power -class MockChassis: - def __init__(self): - self.psu_list = [] - self.fan_drawer_list = [] - self.module_list = [] + def set_maximum_supplied_power(self, supplied_power): + self._max_supplied_power = supplied_power - def get_num_psus(self): - return len(self.psu_list) + def get_status_led(self): + return self._status_led_color - def get_all_psus(self): - return self.psu_list + def set_status_led(self, color): + self._status_led_color = color + return True - def get_psu(self, index): - return self.psu_list[index] + # Methods inherited from DeviceBase class and related setters + def get_name(self): + return self._name - def get_num_fan_drawers(self): - return len(self.fan_drawer_list) + def get_presence(self): + return self._presence - def get_all_fan_drawers(self): - return self.fan_drawer_list + def set_presence(self, presence): + self._presence = presence - def get_num_modules(self): - return len(self.module_list) + def get_model(self): + return self._model - def get_all_modules(self): - return self.module_list + def get_serial(self): + return self._serial + + def get_status(self): + return self._status + + def set_status(self, status): + self._status = status + + def get_position_in_parent(self): + return self._position_in_parent + + def is_replaceable(self): + return self._replaceable diff --git a/sonic-psud/tests/mocked_libs/sonic_platform/__init__.py b/sonic-psud/tests/mocked_libs/sonic_platform/__init__.py new file mode 100644 index 000000000000..188f03fce158 --- /dev/null +++ b/sonic-psud/tests/mocked_libs/sonic_platform/__init__.py @@ -0,0 +1,5 @@ +""" + Mock implementation of sonic_platform package for unit testing +""" + +from . import psu diff --git a/sonic-psud/tests/mocked_libs/sonic_platform/psu.py b/sonic-psud/tests/mocked_libs/sonic_platform/psu.py new file mode 100644 index 000000000000..0e3555c07888 --- /dev/null +++ b/sonic-psud/tests/mocked_libs/sonic_platform/psu.py @@ -0,0 +1,10 @@ +""" + Mock implementation of sonic_platform package for unit testing +""" + +from sonic_platform_base.psu_base import PsuBase + + +class Psu(PsuBase): + def __init__(self): + super(PsuBase, self).__init__() diff --git a/sonic-psud/tests/mocked_libs/swsscommon/__init__.py b/sonic-psud/tests/mocked_libs/swsscommon/__init__.py new file mode 100644 index 000000000000..012af621e5f0 --- /dev/null +++ b/sonic-psud/tests/mocked_libs/swsscommon/__init__.py @@ -0,0 +1,5 @@ +''' + Mock implementation of swsscommon package for unit testing +''' + +from . import swsscommon diff --git a/sonic-psud/tests/mock_swsscommon.py b/sonic-psud/tests/mocked_libs/swsscommon/swsscommon.py similarity index 89% rename from sonic-psud/tests/mock_swsscommon.py rename to sonic-psud/tests/mocked_libs/swsscommon/swsscommon.py index ba8c9d1b262f..6947a8601819 100644 --- a/sonic-psud/tests/mock_swsscommon.py +++ b/sonic-psud/tests/mocked_libs/swsscommon/swsscommon.py @@ -1,3 +1,7 @@ +''' + Mock implementation of swsscommon package for unit testing +''' + STATE_DB = '' @@ -19,6 +23,9 @@ def get(self, key): return self.mock_dict[key] return None + def get_size(self): + return (len(self.mock_dict)) + class FieldValuePairs: fv_dict = {} diff --git a/sonic-psud/tests/test_DaemonPsud.py b/sonic-psud/tests/test_DaemonPsud.py index c905686e490b..1168866577c8 100644 --- a/sonic-psud/tests/test_DaemonPsud.py +++ b/sonic-psud/tests/test_DaemonPsud.py @@ -1,7 +1,7 @@ import datetime import os import sys -from imp import load_source +from imp import load_source # Replace with importlib once we no longer need to support Python 2 import pytest @@ -12,108 +12,167 @@ import mock from sonic_py_common import daemon_base -from . import mock_platform -from . import mock_swsscommon +from .mock_platform import MockChassis, MockFan, MockPsu SYSLOG_IDENTIFIER = 'psud_test' NOT_AVAILABLE = 'N/A' daemon_base.db_connect = mock.MagicMock() -test_path = os.path.dirname(os.path.abspath(__file__)) -modules_path = os.path.dirname(test_path) +tests_path = os.path.dirname(os.path.abspath(__file__)) + +# Add mocked_libs path so that the file under test can load mocked modules from there +mocked_libs_path = os.path.join(tests_path, "mocked_libs") +sys.path.insert(0, mocked_libs_path) + +# Add path to the file under test so that we can load it +modules_path = os.path.dirname(tests_path) scripts_path = os.path.join(modules_path, "scripts") sys.path.insert(0, modules_path) - -os.environ["PSUD_UNIT_TESTING"] = "1" -load_source('psud', scripts_path + '/psud') +load_source('psud', os.path.join(scripts_path, 'psud')) import psud + class TestDaemonPsud(object): """ Test cases to cover functionality in DaemonPsud class """ def test_signal_handler(self): + psud.platform_chassis = MockChassis() daemon_psud = psud.DaemonPsud(SYSLOG_IDENTIFIER) - daemon_psud.stop.set = mock.MagicMock() + daemon_psud.stop_event.set = mock.MagicMock() daemon_psud.log_info = mock.MagicMock() daemon_psud.log_warning = mock.MagicMock() # Test SIGHUP daemon_psud.signal_handler(psud.signal.SIGHUP, None) assert daemon_psud.log_info.call_count == 1 - daemon_psud.log_info.assert_called_with("Caught SIGHUP - ignoring...") + daemon_psud.log_info.assert_called_with("Caught signal 'SIGHUP' - ignoring...") assert daemon_psud.log_warning.call_count == 0 - assert daemon_psud.stop.set.call_count == 0 + assert daemon_psud.stop_event.set.call_count == 0 + assert psud.exit_code == 0 - # Test SIGINT + # Reset daemon_psud.log_info.reset_mock() daemon_psud.log_warning.reset_mock() - daemon_psud.stop.set.reset_mock() - daemon_psud.signal_handler(psud.signal.SIGINT, None) + daemon_psud.stop_event.set.reset_mock() + + # Test SIGINT + test_signal = psud.signal.SIGINT + daemon_psud.signal_handler(test_signal, None) assert daemon_psud.log_info.call_count == 1 - daemon_psud.log_info.assert_called_with("Caught SIGINT - exiting...") + daemon_psud.log_info.assert_called_with("Caught signal 'SIGINT' - exiting...") assert daemon_psud.log_warning.call_count == 0 - assert daemon_psud.stop.set.call_count == 1 + assert daemon_psud.stop_event.set.call_count == 1 + assert psud.exit_code == (128 + test_signal) - # Test SIGTERM + # Reset daemon_psud.log_info.reset_mock() daemon_psud.log_warning.reset_mock() - daemon_psud.stop.set.reset_mock() - daemon_psud.signal_handler(psud.signal.SIGTERM, None) + daemon_psud.stop_event.set.reset_mock() + + # Test SIGTERM + test_signal = psud.signal.SIGTERM + daemon_psud.signal_handler(test_signal, None) assert daemon_psud.log_info.call_count == 1 - daemon_psud.log_info.assert_called_with("Caught SIGTERM - exiting...") + daemon_psud.log_info.assert_called_with("Caught signal 'SIGTERM' - exiting...") assert daemon_psud.log_warning.call_count == 0 - assert daemon_psud.stop.set.call_count == 1 + assert daemon_psud.stop_event.set.call_count == 1 + assert psud.exit_code == (128 + test_signal) - # Test an unhandled signal + # Reset daemon_psud.log_info.reset_mock() daemon_psud.log_warning.reset_mock() - daemon_psud.stop.set.reset_mock() + daemon_psud.stop_event.set.reset_mock() + psud.exit_code = 0 + + # Test an unhandled signal daemon_psud.signal_handler(psud.signal.SIGUSR1, None) assert daemon_psud.log_warning.call_count == 1 - daemon_psud.log_warning.assert_called_with("Caught unhandled signal 'SIGUSR1'") + daemon_psud.log_warning.assert_called_with("Caught unhandled signal 'SIGUSR1' - ignoring...") assert daemon_psud.log_info.call_count == 0 - assert daemon_psud.stop.set.call_count == 0 + assert daemon_psud.stop_event.set.call_count == 0 + assert psud.exit_code == 0 + + def test_run(self): + psud.platform_chassis = MockChassis() + daemon_psud = psud.DaemonPsud(SYSLOG_IDENTIFIER) + daemon_psud._update_psu_entity_info = mock.MagicMock() + daemon_psud.update_psu_data = mock.MagicMock() + daemon_psud._update_led_color = mock.MagicMock() + daemon_psud.update_psu_chassis_info = mock.MagicMock() + + daemon_psud.run() + assert daemon_psud.first_run is False def test_update_psu_data(self): - mock_psu1 = mock_platform.MockPsu(True, True, "PSU 1", 0) - mock_psu2 = mock_platform.MockPsu(True, True, "PSU 2", 1) - mock_psu_tbl = mock.MagicMock() + mock_psu1 = MockPsu("PSU 1", 0, True, True) + mock_psu2 = MockPsu("PSU 2", 1, True, True) + psud.platform_chassis = MockChassis() daemon_psud = psud.DaemonPsud(SYSLOG_IDENTIFIER) daemon_psud._update_single_psu_data = mock.MagicMock() daemon_psud.log_warning = mock.MagicMock() # Test platform_chassis is None psud.platform_chassis = None - daemon_psud.update_psu_data(mock_psu_tbl) + daemon_psud.update_psu_data() assert daemon_psud._update_single_psu_data.call_count == 0 assert daemon_psud.log_warning.call_count == 0 # Test with mocked platform_chassis - psud.platform_chassis = mock_platform.MockChassis() - psud.platform_chassis.psu_list = [mock_psu1, mock_psu2] - daemon_psud.update_psu_data(mock_psu_tbl) + psud.platform_chassis = MockChassis() + psud.platform_chassis._psu_list = [mock_psu1, mock_psu2] + daemon_psud.update_psu_data() assert daemon_psud._update_single_psu_data.call_count == 2 - daemon_psud._update_single_psu_data.assert_called_with(2, mock_psu2, mock_psu_tbl) + expected_calls = [mock.call(1, mock_psu1), mock.call(2, mock_psu2)] + assert daemon_psud._update_single_psu_data.mock_calls == expected_calls assert daemon_psud.log_warning.call_count == 0 - daemon_psud.log_warning = mock.MagicMock() daemon_psud._update_single_psu_data.reset_mock() # Test _update_single_psu_data() throws exception daemon_psud._update_single_psu_data.side_effect = Exception("Test message") - daemon_psud.update_psu_data(mock_psu_tbl) + daemon_psud.update_psu_data() assert daemon_psud._update_single_psu_data.call_count == 2 - daemon_psud._update_single_psu_data.assert_called_with(2, mock_psu2, mock_psu_tbl) + expected_calls = [mock.call(1, mock_psu1), mock.call(2, mock_psu2)] + assert daemon_psud._update_single_psu_data.mock_calls == expected_calls assert daemon_psud.log_warning.call_count == 2 - daemon_psud.log_warning.assert_called_with("Failed to update PSU data - Test message") + expected_calls = [mock.call("Failed to update PSU data - Test message")] * 2 + assert daemon_psud.log_warning.mock_calls == expected_calls + + @mock.patch('psud._wrapper_get_psu_presence', mock.MagicMock()) + @mock.patch('psud._wrapper_get_psu_status', mock.MagicMock()) + def test_update_single_psu_data(self): + psud._wrapper_get_psu_presence.return_value = True + psud._wrapper_get_psu_status.return_value = True + + psu1 = MockPsu('PSU 1', 0, True, 'Fake Model', '12345678') + psud.platform_chassis = MockChassis() + psud.platform_chassis._psu_list.append(psu1) + + expected_fvp = psud.swsscommon.FieldValuePairs( + [(psud.PSU_INFO_MODEL_FIELD, 'Fake Model'), + (psud.PSU_INFO_SERIAL_FIELD, '12345678'), + (psud.PSU_INFO_TEMP_FIELD, '30.0'), + (psud.PSU_INFO_TEMP_TH_FIELD, '50.0'), + (psud.PSU_INFO_VOLTAGE_FIELD, '12.0'), + (psud.PSU_INFO_VOLTAGE_MIN_TH_FIELD, '11.0'), + (psud.PSU_INFO_VOLTAGE_MAX_TH_FIELD, '13.0'), + (psud.PSU_INFO_CURRENT_FIELD, '8.0'), + (psud.PSU_INFO_POWER_FIELD, '100.0'), + (psud.PSU_INFO_FRU_FIELD, 'True'), + ]) + + daemon_psud = psud.DaemonPsud(SYSLOG_IDENTIFIER) + daemon_psud.psu_tbl = mock.MagicMock() + daemon_psud._update_single_psu_data(1, psu1) + daemon_psud.psu_tbl.set.assert_called_with(psud.PSU_INFO_KEY_TEMPLATE.format(1), expected_fvp) def test_set_psu_led(self): mock_logger = mock.MagicMock() - mock_psu = mock_platform.MockPsu(True, True, "PSU 1", 0) + mock_psu = MockPsu("PSU 1", 0, True, True) psu_status = psud.PsuStatus(mock_logger, mock_psu) daemon_psud = psud.DaemonPsud(SYSLOG_IDENTIFIER) @@ -158,82 +217,82 @@ def test_set_psu_led(self): assert mock_psu.get_status_led() == mock_psu.STATUS_LED_COLOR_GREEN # Test set_status_led not implemented - mock_psu.set_status_led = mock.MagicMock(side_effect = NotImplementedError) + mock_psu.set_status_led = mock.MagicMock(side_effect=NotImplementedError) daemon_psud.log_warning = mock.MagicMock() daemon_psud._set_psu_led(mock_psu, psu_status) assert daemon_psud.log_warning.call_count == 1 daemon_psud.log_warning.assert_called_with("set_status_led() not implemented") def test_update_led_color(self): - mock_psu = mock_platform.MockPsu(True, True, "PSU 1", 0) - mock_psu_tbl = mock.MagicMock() + mock_psu = MockPsu("PSU 1", 0, True, True) mock_logger = mock.MagicMock() psu_status = psud.PsuStatus(mock_logger, mock_psu) daemon_psud = psud.DaemonPsud(SYSLOG_IDENTIFIER) + daemon_psud.psu_tbl = mock.MagicMock() daemon_psud._update_psu_fan_led_status = mock.MagicMock() # If psud.platform_chassis is None, _update_psu_fan_led_status() should do nothing psud.platform_chassis = None - daemon_psud._update_led_color(mock_psu_tbl) - assert mock_psu_tbl.set.call_count == 0 + daemon_psud._update_led_color() + assert daemon_psud.psu_tbl.set.call_count == 0 assert daemon_psud._update_psu_fan_led_status.call_count == 0 - psud.platform_chassis = mock_platform.MockChassis() + psud.platform_chassis = MockChassis() daemon_psud.psu_status_dict[1] = psu_status - expected_fvp = psud.swsscommon.FieldValuePairs([('led_status', mock_platform.MockPsu.STATUS_LED_COLOR_OFF)]) - daemon_psud._update_led_color(mock_psu_tbl) - assert mock_psu_tbl.set.call_count == 1 - mock_psu_tbl.set.assert_called_with(psud.PSU_INFO_KEY_TEMPLATE.format(1), expected_fvp) + expected_fvp = psud.swsscommon.FieldValuePairs([('led_status', MockPsu.STATUS_LED_COLOR_OFF)]) + daemon_psud._update_led_color() + assert daemon_psud.psu_tbl.set.call_count == 1 + daemon_psud.psu_tbl.set.assert_called_with(psud.PSU_INFO_KEY_TEMPLATE.format(1), expected_fvp) assert daemon_psud._update_psu_fan_led_status.call_count == 1 daemon_psud._update_psu_fan_led_status.assert_called_with(mock_psu, 1) - mock_psu_tbl.set.reset_mock() + daemon_psud.psu_tbl.reset_mock() daemon_psud._update_psu_fan_led_status.reset_mock() - mock_psu.set_status_led(mock_platform.MockPsu.STATUS_LED_COLOR_GREEN) - expected_fvp = psud.swsscommon.FieldValuePairs([('led_status', mock_platform.MockPsu.STATUS_LED_COLOR_GREEN)]) - daemon_psud._update_led_color(mock_psu_tbl) - assert mock_psu_tbl.set.call_count == 1 - mock_psu_tbl.set.assert_called_with(psud.PSU_INFO_KEY_TEMPLATE.format(1), expected_fvp) + mock_psu.set_status_led(MockPsu.STATUS_LED_COLOR_GREEN) + expected_fvp = psud.swsscommon.FieldValuePairs([('led_status', MockPsu.STATUS_LED_COLOR_GREEN)]) + daemon_psud._update_led_color() + assert daemon_psud.psu_tbl.set.call_count == 1 + daemon_psud.psu_tbl.set.assert_called_with(psud.PSU_INFO_KEY_TEMPLATE.format(1), expected_fvp) assert daemon_psud._update_psu_fan_led_status.call_count == 1 daemon_psud._update_psu_fan_led_status.assert_called_with(mock_psu, 1) - mock_psu_tbl.set.reset_mock() + daemon_psud.psu_tbl.reset_mock() daemon_psud._update_psu_fan_led_status.reset_mock() - mock_psu.set_status_led(mock_platform.MockPsu.STATUS_LED_COLOR_RED) - expected_fvp = psud.swsscommon.FieldValuePairs([('led_status', mock_platform.MockPsu.STATUS_LED_COLOR_RED)]) - daemon_psud._update_led_color(mock_psu_tbl) - assert mock_psu_tbl.set.call_count == 1 - mock_psu_tbl.set.assert_called_with(psud.PSU_INFO_KEY_TEMPLATE.format(1), expected_fvp) + mock_psu.set_status_led(MockPsu.STATUS_LED_COLOR_RED) + expected_fvp = psud.swsscommon.FieldValuePairs([('led_status', MockPsu.STATUS_LED_COLOR_RED)]) + daemon_psud._update_led_color() + assert daemon_psud.psu_tbl.set.call_count == 1 + daemon_psud.psu_tbl.set.assert_called_with(psud.PSU_INFO_KEY_TEMPLATE.format(1), expected_fvp) assert daemon_psud._update_psu_fan_led_status.call_count == 1 daemon_psud._update_psu_fan_led_status.assert_called_with(mock_psu, 1) - mock_psu_tbl.set.reset_mock() + daemon_psud.psu_tbl.reset_mock() daemon_psud._update_psu_fan_led_status.reset_mock() # Test exception handling - mock_psu.get_status_led = mock.Mock(side_effect = NotImplementedError) + mock_psu.get_status_led = mock.Mock(side_effect=NotImplementedError) expected_fvp = psud.swsscommon.FieldValuePairs([('led_status', psud.NOT_AVAILABLE)]) - daemon_psud._update_led_color(mock_psu_tbl) - assert mock_psu_tbl.set.call_count == 1 - mock_psu_tbl.set.assert_called_with(psud.PSU_INFO_KEY_TEMPLATE.format(1), expected_fvp) + daemon_psud._update_led_color() + assert daemon_psud.psu_tbl.set.call_count == 1 + daemon_psud.psu_tbl.set.assert_called_with(psud.PSU_INFO_KEY_TEMPLATE.format(1), expected_fvp) assert daemon_psud._update_psu_fan_led_status.call_count == 1 daemon_psud._update_psu_fan_led_status.assert_called_with(mock_psu, 1) def test_update_psu_fan_led_status(self): - mock_fan = mock_platform.MockFan("PSU 1 Test Fan 1", mock_platform.MockFan.FAN_DIRECTION_INTAKE) - mock_psu = mock_platform.MockPsu(True, True, "PSU 1", 0) + mock_fan = MockFan("PSU 1 Test Fan 1", MockFan.FAN_DIRECTION_INTAKE) + mock_psu = MockPsu("PSU 1", 0, True, True) mock_psu._fan_list = [mock_fan] mock_logger = mock.MagicMock() - psud.platform_chassis = mock_platform.MockChassis() + psud.platform_chassis = MockChassis() daemon_psud = psud.DaemonPsud(SYSLOG_IDENTIFIER) daemon_psud.fan_tbl = mock.MagicMock() - expected_fvp = psud.swsscommon.FieldValuePairs([(psud.FAN_INFO_LED_STATUS_FIELD, mock_platform.MockFan.STATUS_LED_COLOR_OFF)]) + expected_fvp = psud.swsscommon.FieldValuePairs([(psud.FAN_INFO_LED_STATUS_FIELD, MockFan.STATUS_LED_COLOR_OFF)]) daemon_psud._update_psu_fan_led_status(mock_psu, 1) assert daemon_psud.fan_tbl.set.call_count == 1 daemon_psud.fan_tbl.set.assert_called_with("PSU 1 Test Fan 1", expected_fvp) @@ -241,7 +300,7 @@ def test_update_psu_fan_led_status(self): daemon_psud.fan_tbl.set.reset_mock() # Test Fan.get_status_led not implemented - mock_fan.get_status_led = mock.Mock(side_effect = NotImplementedError) + mock_fan.get_status_led = mock.Mock(side_effect=NotImplementedError) expected_fvp = psud.swsscommon.FieldValuePairs([(psud.FAN_INFO_LED_STATUS_FIELD, psud.NOT_AVAILABLE)]) daemon_psud._update_psu_fan_led_status(mock_psu, 1) assert daemon_psud.fan_tbl.set.call_count == 1 @@ -250,7 +309,7 @@ def test_update_psu_fan_led_status(self): daemon_psud.fan_tbl.set.reset_mock() # Test Fan.get_name not implemented - mock_fan.get_name = mock.Mock(side_effect = NotImplementedError) + mock_fan.get_name = mock.Mock(side_effect=NotImplementedError) daemon_psud._update_psu_fan_led_status(mock_psu, 1) assert daemon_psud.fan_tbl.set.call_count == 1 daemon_psud.fan_tbl.set.assert_called_with("PSU 1 FAN 1", expected_fvp) @@ -262,19 +321,18 @@ def test_update_psu_chassis_info(self): # If psud.platform_chassis is None, update_psu_chassis_info() should do nothing psud.platform_chassis = None daemon_psud.psu_chassis_info = None - daemon_psud.update_psu_chassis_info(None) + daemon_psud.update_psu_chassis_info() assert daemon_psud.psu_chassis_info is None # Now we mock platform_chassis, so that daemon_psud.psu_chassis_info should be instantiated and run_power_budget() should be called - psud.platform_chassis = mock_platform.MockChassis() - daemon_psud.update_psu_chassis_info(None) + psud.platform_chassis = MockChassis() + daemon_psud.update_psu_chassis_info() assert daemon_psud.psu_chassis_info is not None assert daemon_psud.psu_chassis_info.run_power_budget.call_count == 1 - daemon_psud.psu_chassis_info.run_power_budget.assert_called_with(None) def test_update_psu_entity_info(self): - mock_psu1 = mock_platform.MockPsu(True, True, "PSU 1", 0) - mock_psu2 = mock_platform.MockPsu(True, True, "PSU 2", 1) + mock_psu1 = MockPsu("PSU 1", 0, True, True) + mock_psu2 = MockPsu("PSU 2", 1, True, True) daemon_psud = psud.DaemonPsud(SYSLOG_IDENTIFIER) daemon_psud._update_single_psu_entity_info = mock.MagicMock() @@ -284,23 +342,24 @@ def test_update_psu_entity_info(self): daemon_psud._update_psu_entity_info() assert daemon_psud._update_single_psu_entity_info.call_count == 0 - psud.platform_chassis = mock_platform.MockChassis() - psud.platform_chassis.psu_list = [mock_psu1] + psud.platform_chassis = MockChassis() + psud.platform_chassis._psu_list = [mock_psu1] daemon_psud._update_psu_entity_info() assert daemon_psud._update_single_psu_entity_info.call_count == 1 daemon_psud._update_single_psu_entity_info.assert_called_with(1, mock_psu1) daemon_psud._update_single_psu_entity_info.reset_mock() - psud.platform_chassis.psu_list = [mock_psu1, mock_psu2] + psud.platform_chassis._psu_list = [mock_psu1, mock_psu2] daemon_psud._update_psu_entity_info() assert daemon_psud._update_single_psu_entity_info.call_count == 2 - daemon_psud._update_single_psu_entity_info.assert_called_with(2, mock_psu2) + expected_calls = [mock.call(1, mock_psu1), mock.call(2, mock_psu2)] + assert daemon_psud._update_single_psu_entity_info.mock_calls == expected_calls # Test behavior if _update_single_psu_entity_info raises an exception daemon_psud._update_single_psu_entity_info.reset_mock() daemon_psud._update_single_psu_entity_info.side_effect = Exception("Test message") daemon_psud.log_warning = mock.MagicMock() - psud.platform_chassis.psu_list = [mock_psu1] + psud.platform_chassis._psu_list = [mock_psu1] daemon_psud._update_psu_entity_info() assert daemon_psud._update_single_psu_entity_info.call_count == 1 daemon_psud._update_single_psu_entity_info.assert_called_with(1, mock_psu1) @@ -309,7 +368,7 @@ def test_update_psu_entity_info(self): @mock.patch('psud.try_get', mock.MagicMock(return_value=0)) def test_update_single_psu_entity_info(self): - mock_psu1 = mock_platform.MockPsu(True, True, "PSU 1", 0) + mock_psu1 = MockPsu("PSU 1", 0, True, True) expected_fvp = psud.swsscommon.FieldValuePairs( [('position_in_parent', '0'), @@ -327,23 +386,23 @@ def test_update_psu_fan_data(self, mock_datetime): fake_time = datetime.datetime(2021, 1, 1, 12, 34, 56) mock_datetime.now.return_value = fake_time - mock_fan = mock_platform.MockFan("PSU 1 Test Fan 1", mock_platform.MockFan.FAN_DIRECTION_INTAKE) - mock_psu1 = mock_platform.MockPsu(True, True, "PSU 1", 0) + mock_fan = MockFan("PSU 1 Test Fan 1", MockFan.FAN_DIRECTION_INTAKE) + mock_psu1 = MockPsu("PSU 1", 0, True, True) mock_psu1._fan_list = [mock_fan] mock_logger = mock.MagicMock() - psud.platform_chassis = mock_platform.MockChassis() - psud.platform_chassis.psu_list = [mock_psu1] + psud.platform_chassis = MockChassis() + psud.platform_chassis._psu_list = [mock_psu1] daemon_psud = psud.DaemonPsud(SYSLOG_IDENTIFIER) daemon_psud.fan_tbl = mock.MagicMock() expected_fvp = psud.swsscommon.FieldValuePairs( - [(psud.FAN_INFO_PRESENCE_FIELD, "True"), - (psud.FAN_INFO_STATUS_FIELD, "True"), - (psud.FAN_INFO_DIRECTION_FIELD, mock_fan.get_direction()), - (psud.FAN_INFO_SPEED_FIELD, str(mock_fan.get_speed())), - (psud.FAN_INFO_TIMESTAMP_FIELD, fake_time.strftime('%Y%m%d %H:%M:%S')) + [(psud.FAN_INFO_PRESENCE_FIELD, "True"), + (psud.FAN_INFO_STATUS_FIELD, "True"), + (psud.FAN_INFO_DIRECTION_FIELD, mock_fan.get_direction()), + (psud.FAN_INFO_SPEED_FIELD, str(mock_fan.get_speed())), + (psud.FAN_INFO_TIMESTAMP_FIELD, fake_time.strftime('%Y%m%d %H:%M:%S')) ]) daemon_psud._update_psu_fan_data(mock_psu1, 1) assert daemon_psud.fan_tbl.set.call_count == 1 diff --git a/sonic-psud/tests/test_PsuChassisInfo.py b/sonic-psud/tests/test_PsuChassisInfo.py index 2757f3e557bb..4231b497ec5e 100644 --- a/sonic-psud/tests/test_PsuChassisInfo.py +++ b/sonic-psud/tests/test_PsuChassisInfo.py @@ -1,6 +1,6 @@ import os import sys -from imp import load_source +from imp import load_source # Replace with importlib once we no longer need to support Python 2 import pytest @@ -11,7 +11,6 @@ import mock from sonic_py_common import daemon_base -from . import mock_swsscommon from .mock_platform import MockChassis, MockPsu, MockFanDrawer, MockModule SYSLOG_IDENTIFIER = 'test_PsuChassisInfo' @@ -19,15 +18,24 @@ daemon_base.db_connect = mock.MagicMock() -test_path = os.path.dirname(os.path.abspath(__file__)) -modules_path = os.path.dirname(test_path) +tests_path = os.path.dirname(os.path.abspath(__file__)) + +# Add mocked_libs path so that the file under test can load mocked modules from there +mocked_libs_path = os.path.join(tests_path, "mocked_libs") +sys.path.insert(0, mocked_libs_path) + +# We also need to load the mocked swsscommon locally for use below +load_source('swsscommon', os.path.join(mocked_libs_path, 'swsscommon', 'swsscommon.py')) +import swsscommon as mock_swsscommon + +# Add path to the file under test so that we can load it +modules_path = os.path.dirname(tests_path) scripts_path = os.path.join(modules_path, "scripts") sys.path.insert(0, modules_path) - -os.environ["PSUD_UNIT_TESTING"] = "1" -load_source('psud', scripts_path + '/psud') +load_source('psud', os.path.join(scripts_path, 'psud')) import psud + CHASSIS_INFO_TABLE = 'CHASSIS_INFO' CHASSIS_INFO_KEY_TEMPLATE = 'chassis {}' @@ -52,6 +60,7 @@ class TestPsuChassisInfo(object): """ Test cases to cover functionality in PsuChassisInfo class """ + def test_update_master_status(self): chassis = MockChassis() chassis_info = psud.PsuChassisInfo(SYSLOG_IDENTIFIER, chassis) @@ -97,33 +106,22 @@ def test_update_master_status(self): assert ret == False assert chassis_info.master_status_good == False - # Test set_status_master_led not implemented - with mock.patch.object(MockPsu, "set_status_master_led", mock.Mock(side_effect = NotImplementedError)): - chassis_info.total_supplied_power = 510.0 - chassis_info.total_consumed_power = 350.0 - chassis_info.master_status_good = False - chassis_info.log_warning = mock.MagicMock() - ret = chassis_info.update_master_status() - assert ret == True - assert chassis_info.master_status_good == True - chassis_info.log_warning.assert_called_with("set_status_master_led() not implemented") - def test_supplied_power(self): chassis = MockChassis() - psu1 = MockPsu(True, True, "PSU 1", 0) + psu1 = MockPsu("PSU 1", 0, True, True) psu1_power = 510.0 psu1.set_maximum_supplied_power(psu1_power) - chassis.psu_list.append(psu1) + chassis._psu_list.append(psu1) - psu2 = MockPsu(True, True, "PSU 2", 1) + psu2 = MockPsu("PSU 2", 1, True, True) psu2_power = 800.0 psu2.set_maximum_supplied_power(psu2_power) - chassis.psu_list.append(psu2) + chassis._psu_list.append(psu2) - psu3 = MockPsu(True, True, "PSU 3", 2) + psu3 = MockPsu("PSU 3", 2, True, True) psu3_power = 350.0 psu3.set_maximum_supplied_power(psu3_power) - chassis.psu_list.append(psu3) + chassis._psu_list.append(psu3) total_power = psu1_power + psu2_power + psu3_power state_db = daemon_base.db_connect("STATE_DB") @@ -151,15 +149,15 @@ def test_supplied_power(self): def test_consumed_power(self): chassis = MockChassis() - fan_drawer1 = MockFanDrawer(True, True, "FanDrawer 1") + fan_drawer1 = MockFanDrawer("FanDrawer 1", 0, True, True) fan_drawer1_power = 510.0 fan_drawer1.set_maximum_consumed_power(fan_drawer1_power) - chassis.fan_drawer_list.append(fan_drawer1) + chassis._fan_drawer_list.append(fan_drawer1) - module1 = MockFanDrawer(True, True, "Module 1") + module1 = MockModule("Module 1", 0, True, True) module1_power = 700.0 module1.set_maximum_consumed_power(module1_power) - chassis.module_list.append(module1) + chassis._module_list.append(module1) total_power = fan_drawer1_power + module1_power state_db = daemon_base.db_connect("STATE_DB") @@ -186,58 +184,63 @@ def test_consumed_power(self): fvs = chassis_tbl.get(CHASSIS_INFO_POWER_KEY_TEMPLATE.format(1)) assert total_power == float(fvs[CHASSIS_INFO_TOTAL_POWER_CONSUMED_FIELD]) - def test_power_budget(self): chassis = MockChassis() - psu = MockPsu(True, True, "PSU 1", 0) + psu1 = MockPsu("PSU 1", 0, True, True) psu1_power = 510.0 - psu.set_maximum_supplied_power(psu1_power) - chassis.psu_list.append(psu) + psu1.set_maximum_supplied_power(psu1_power) + chassis._psu_list.append(psu1) - fan_drawer1 = MockFanDrawer(True, True, "FanDrawer 1") + fan_drawer1 = MockFanDrawer("FanDrawer 1", 0, True, True) fan_drawer1_power = 510.0 fan_drawer1.set_maximum_consumed_power(fan_drawer1_power) - chassis.fan_drawer_list.append(fan_drawer1) + chassis._fan_drawer_list.append(fan_drawer1) - module1 = MockFanDrawer(True, True, "Module 1") + module1 = MockModule("Module 1", 0, True, True) module1_power = 700.0 module1.set_maximum_consumed_power(module1_power) - chassis.module_list.append(module1) + chassis._module_list.append(module1) state_db = daemon_base.db_connect("STATE_DB") chassis_tbl = mock_swsscommon.Table(state_db, CHASSIS_INFO_TABLE) chassis_info = psud.PsuChassisInfo(SYSLOG_IDENTIFIER, chassis) - # Check if supplied_power < consumed_power + # Check case where supplied_power < consumed_power chassis_info.run_power_budget(chassis_tbl) chassis_info.update_master_status() fvs = chassis_tbl.get(CHASSIS_INFO_POWER_KEY_TEMPLATE.format(1)) assert float(fvs[CHASSIS_INFO_TOTAL_POWER_SUPPLIED_FIELD]) < float(fvs[CHASSIS_INFO_TOTAL_POWER_CONSUMED_FIELD]) assert chassis_info.master_status_good == False - assert MockPsu.get_status_master_led() == MockPsu.STATUS_LED_COLOR_RED + + # We cannot call get_status_master_led() on our mocked PSUs, because + # they are not instantiated from the same Psu class loaded in psud, + # so we must call it on the class there. + assert psud.Psu.get_status_master_led() == MockPsu.STATUS_LED_COLOR_RED # Add a PSU - psu = MockPsu(True, True, "PSU 2", 1) + psu2 = MockPsu("PSU 2", 1, True, True) psu2_power = 800.0 - psu.set_maximum_supplied_power(psu2_power) - chassis.psu_list.append(psu) + psu2.set_maximum_supplied_power(psu2_power) + chassis._psu_list.append(psu2) - # Check if supplied_power > consumed_power + # Check case where supplied_power > consumed_power chassis_info.run_power_budget(chassis_tbl) chassis_info.update_master_status() fvs = chassis_tbl.get(CHASSIS_INFO_POWER_KEY_TEMPLATE.format(1)) assert float(fvs[CHASSIS_INFO_TOTAL_POWER_SUPPLIED_FIELD]) > float(fvs[CHASSIS_INFO_TOTAL_POWER_CONSUMED_FIELD]) assert chassis_info.master_status_good == True - assert MockPsu.get_status_master_led() == MockPsu.STATUS_LED_COLOR_GREEN + # We cannot call get_status_master_led() on our mocked PSUs, because + # they are not instantiated from the same Psu class loaded in psud, + # so we must call it on the class there. + assert psud.Psu.get_status_master_led() == MockPsu.STATUS_LED_COLOR_GREEN def test_get_psu_key(self): assert psud.get_psu_key(0) == psud.PSU_INFO_KEY_TEMPLATE.format(0) assert psud.get_psu_key(1) == psud.PSU_INFO_KEY_TEMPLATE.format(1) - def test_try_get(self): # Test a proper, working callback GOOD_CALLBACK_RETURN_VALUE = "This is a test" diff --git a/sonic-psud/tests/test_PsuStatus.py b/sonic-psud/tests/test_PsuStatus.py index e55384d8d239..de7fca542940 100644 --- a/sonic-psud/tests/test_PsuStatus.py +++ b/sonic-psud/tests/test_PsuStatus.py @@ -1,6 +1,6 @@ import os import sys -from imp import load_source +from imp import load_source # Replace with importlib once we no longer need to support Python 2 # TODO: Clean this up once we no longer need to support Python 2 if sys.version_info.major == 3: @@ -10,13 +10,17 @@ from .mock_platform import MockPsu -test_path = os.path.dirname(os.path.abspath(__file__)) -modules_path = os.path.dirname(test_path) +tests_path = os.path.dirname(os.path.abspath(__file__)) + +# Add mocked_libs path so that the file under test can load mocked modules from there +mocked_libs_path = os.path.join(tests_path, "mocked_libs") +sys.path.insert(0, mocked_libs_path) + +# Add path to the file under test so that we can load it +modules_path = os.path.dirname(tests_path) scripts_path = os.path.join(modules_path, "scripts") sys.path.insert(0, modules_path) - -os.environ["PSUD_UNIT_TESTING"] = "1" -load_source('psud', scripts_path + '/psud') +load_source('psud', os.path.join(scripts_path, 'psud')) import psud @@ -27,7 +31,7 @@ class TestPsuStatus(object): def test_set_presence(self): mock_logger = mock.MagicMock() - mock_psu = MockPsu(True, True, "PSU 1", 0) + mock_psu = MockPsu("PSU 1", 0, True, True) psu_status = psud.PsuStatus(mock_logger, mock_psu) assert psu_status.presence == False @@ -49,7 +53,7 @@ def test_set_presence(self): def test_set_power_good(self): mock_logger = mock.MagicMock() - mock_psu = MockPsu(True, True, "PSU 1", 0) + mock_psu = MockPsu("PSU 1", 0, True, True) psu_status = psud.PsuStatus(mock_logger, mock_psu) assert psu_status.power_good == False @@ -76,7 +80,7 @@ def test_set_power_good(self): def test_set_voltage(self): mock_logger = mock.MagicMock() - mock_psu = MockPsu(True, True, "PSU 1", 0) + mock_psu = MockPsu("PSU 1", 0, True, True) psu_status = psud.PsuStatus(mock_logger, mock_psu) assert psu_status.voltage_good == False @@ -143,7 +147,7 @@ def test_set_voltage(self): def test_set_temperature(self): mock_logger = mock.MagicMock() - mock_psu = MockPsu(True, True, "PSU 1", 0) + mock_psu = MockPsu("PSU 1", 0, True, True) psu_status = psud.PsuStatus(mock_logger, mock_psu) assert psu_status.temperature_good == False @@ -193,7 +197,7 @@ def test_set_temperature(self): def test_is_ok(self): mock_logger = mock.MagicMock() - mock_psu = MockPsu(True, True, "PSU 1", 0) + mock_psu = MockPsu("PSU 1", 0, True, True) psu_status = psud.PsuStatus(mock_logger, mock_psu) psu_status.presence = True diff --git a/sonic-psud/tests/test_psud.py b/sonic-psud/tests/test_psud.py index a8d36fd84ac9..a66a8c29ace0 100644 --- a/sonic-psud/tests/test_psud.py +++ b/sonic-psud/tests/test_psud.py @@ -1,6 +1,6 @@ import os import sys -from imp import load_source +from imp import load_source # Replace with importlib once we no longer need to support Python 2 import pytest @@ -11,23 +11,29 @@ import mock from sonic_py_common import daemon_base -from . import mock_swsscommon -from .mock_platform import MockChassis, MockPsu, MockFanDrawer, MockModule +from .mock_platform import MockPsu -SYSLOG_IDENTIFIER = 'psud_test' -NOT_AVAILABLE = 'N/A' +tests_path = os.path.dirname(os.path.abspath(__file__)) -daemon_base.db_connect = mock.MagicMock() +# Add mocked_libs path so that the file under test can load mocked modules from there +mocked_libs_path = os.path.join(tests_path, "mocked_libs") +sys.path.insert(0, mocked_libs_path) -test_path = os.path.dirname(os.path.abspath(__file__)) -modules_path = os.path.dirname(test_path) +# Add path to the file under test so that we can load it +modules_path = os.path.dirname(tests_path) scripts_path = os.path.join(modules_path, "scripts") sys.path.insert(0, modules_path) - -os.environ["PSUD_UNIT_TESTING"] = "1" -load_source('psud', scripts_path + '/psud') +load_source('psud', os.path.join(scripts_path, 'psud')) import psud + +daemon_base.db_connect = mock.MagicMock() + + +SYSLOG_IDENTIFIER = 'psud_test' +NOT_AVAILABLE = 'N/A' + + @mock.patch('psud.platform_chassis', mock.MagicMock()) @mock.patch('psud.platform_psuutil', mock.MagicMock()) def test_wrapper_get_num_psus(): @@ -171,3 +177,11 @@ def test_log_on_status_changed(): assert mock_logger.log_notice.call_count == 0 assert mock_logger.log_warning.call_count == 1 mock_logger.log_warning.assert_called_with(abnormal_log) + + +@mock.patch('psud.DaemonPsud.run') +def test_main(mock_run): + mock_run.return_value = False + + psud.main() + assert mock_run.call_count == 1