Skip to content

Commit

Permalink
[psud] Increase unit test coverage (sonic-net#140)
Browse files Browse the repository at this point in the history
- Add 100% unit test coverage of `PsuStatus` class in psud.
- Add skeleton of class to test `DaemonPsud` class
- Add test case for `get_psu_key()` and `try_get()` helper functions
- Add checks to import 'mock' from the 'unittest' package if running with Python 3

Overall psud unit test coverage increases from 39% to 51%.

Previous unit test coverage:

```
----------- coverage: platform linux, python 3.7.3-final-0 -----------
Name           Stmts   Miss  Cover
----------------------------------
scripts/psud     381    233    39%
Coverage HTML written to dir htmlcov
Coverage XML written to file coverage.xml
```

Unit test coverage with this patch:

```
----------- coverage: platform linux, python 3.7.3-final-0 -----------
Name           Stmts   Miss  Cover
----------------------------------
scripts/psud     381    185    51%
Coverage HTML written to dir htmlcov
Coverage XML written to file coverage.xml
```
  • Loading branch information
jleveque authored Jan 22, 2021
1 parent 81318f7 commit ad0a5ad
Show file tree
Hide file tree
Showing 4 changed files with 334 additions and 2 deletions.
2 changes: 1 addition & 1 deletion sonic-psud/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
'wheel'
],
tests_require=[
'mock>=2.0.0; python_version < "3.3"',
'pytest',
'mock>=2.0.0',
'pytest-cov'
],
classifiers=[
Expand Down
8 changes: 8 additions & 0 deletions sonic-psud/tests/mock_platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,18 @@ def __init__(self, psu_presence, psu_status, psu_name):
self.name = psu_name
self.presence = True
self.psu_status = psu_status
self.status_led_color = self.STATUS_LED_COLOR_OFF

def get_powergood_status(self):
return self.psu_status

def set_status_led(self, color):
self.status_led_color = color
return True

def get_status_led(self):
return self.status_led_color

def set_status(self, status):
self.psu_status = status

Expand Down
236 changes: 236 additions & 0 deletions sonic-psud/tests/test_PsuStatus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
import os
import sys
from imp import load_source

# TODO: Clean this up once we no longer need to support Python 2
if sys.version_info.major == 3:
from unittest.mock import MagicMock
else:
from mock import MagicMock

from .mock_platform import MockPsu

test_path = os.path.dirname(os.path.abspath(__file__))
modules_path = os.path.dirname(test_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')
from psud import *


class TestPsuStatus(object):
"""
Test cases to cover functionality of PsuStatus class
"""

def test_set_presence(self):
mock_logger = MagicMock()
mock_psu = MockPsu(True, True, "PSU 1")

psu_status = PsuStatus(mock_logger, mock_psu)
assert psu_status.presence == False

# Test toggling presence to True
ret = psu_status.set_presence(True)
assert ret == True
assert psu_status.presence == True

# Test toggling presence to False
ret = psu_status.set_presence(False)
assert ret == True
assert psu_status.presence == False

# Test attempting to set presence to the same as the current value
ret = psu_status.set_presence(False)
assert ret == False
assert psu_status.presence == False

def test_set_power_good(self):
mock_logger = MagicMock()
mock_psu = MockPsu(True, True, "PSU 1")

psu_status = PsuStatus(mock_logger, mock_psu)
assert psu_status.power_good == False

# Test toggling power_good to True
ret = psu_status.set_power_good(True)
assert ret == True
assert psu_status.power_good == True

# Test attempting to set power_good to the same as the current value (return value should be False)
ret = psu_status.set_power_good(True)
assert ret == False
assert psu_status.power_good == True

# Test toggling power_good to False
ret = psu_status.set_power_good(False)
assert ret == True
assert psu_status.power_good == False

# Test attempting to set power_good to the same as the current value (return value should be False)
ret = psu_status.set_power_good(False)
assert ret == False
assert psu_status.power_good == False

def test_set_voltage(self):
mock_logger = MagicMock()
mock_psu = MockPsu(True, True, "PSU 1")

psu_status = PsuStatus(mock_logger, mock_psu)
assert psu_status.voltage_good == False

# Pass in a good voltage
ret = psu_status.set_voltage(12.0, 12.5, 11.5)
assert ret == True
assert psu_status.voltage_good == True

# Pass in a another good voltage successively (return value should be False)
ret = psu_status.set_voltage(11.9, 12.5, 11.5)
assert ret == False
assert psu_status.voltage_good == True

# Pass in a high voltage
ret = psu_status.set_voltage(12.6, 12.5, 11.5)
assert ret == True
assert psu_status.voltage_good == False

# Pass in a another bad voltage successively (return value should be False)
ret = psu_status.set_voltage(12.7, 12.5, 11.5)
assert ret == False
assert psu_status.voltage_good == False

# Pass in a good (high edge case) voltage
ret = psu_status.set_voltage(12.5, 12.5, 11.5)
assert ret == True
assert psu_status.voltage_good == True

# Pass in a low voltage
ret = psu_status.set_voltage(11.4, 12.5, 11.5)
assert ret == True
assert psu_status.voltage_good == False

# Pass in a good (low edge case) voltage
ret = psu_status.set_voltage(11.5, 12.5, 11.5)
assert ret == True
assert psu_status.voltage_good == True

# Test passing parameters as None when voltage_good == True
ret = psu_status.set_voltage(None, 12.5, 11.5)
assert ret == False
assert psu_status.voltage_good == True
ret = psu_status.set_voltage(11.5, None, 11.5)
assert ret == False
assert psu_status.voltage_good == True
ret = psu_status.set_voltage(11.5, 12.5, None)
assert ret == False
assert psu_status.voltage_good == True

# Test passing parameters as None when voltage_good == False
psu_status.voltage_good = False
ret = psu_status.set_voltage(None, 12.5, 11.5)
assert ret == False
assert psu_status.voltage_good == True
psu_status.voltage_good = False
ret = psu_status.set_voltage(11.5, None, 11.5)
assert ret == False
assert psu_status.voltage_good == True
psu_status.voltage_good = False
ret = psu_status.set_voltage(11.5, 12.5, None)
assert ret == False
assert psu_status.voltage_good == True

def test_set_temperature(self):
mock_logger = MagicMock()
mock_psu = MockPsu(True, True, "PSU 1")

psu_status = PsuStatus(mock_logger, mock_psu)
assert psu_status.temperature_good == False

# Pass in a good temperature
ret = psu_status.set_temperature(20.123, 50.0)
assert ret == True
assert psu_status.temperature_good == True

# Pass in a another good temperature successively (return value should be False)
ret = psu_status.set_temperature(31.456, 50.0)
assert ret == False
assert psu_status.temperature_good == True

# Pass in a high temperature
ret = psu_status.set_temperature(50.001, 50.0)
assert ret == True
assert psu_status.temperature_good == False

# Pass in a another bad temperature successively (return value should be False)
ret = psu_status.set_temperature(50.0, 50.0)
assert ret == False
assert psu_status.temperature_good == False

# Pass in a good (high edge case) temperature
ret = psu_status.set_temperature(49.999, 50.0)
assert ret == True
assert psu_status.temperature_good == True

# Test passing parameters as None when temperature_good == True
ret = psu_status.set_temperature(None, 50.0)
assert ret == False
assert psu_status.temperature_good == True
ret = psu_status.set_temperature(20.123, None)
assert ret == False
assert psu_status.temperature_good == True

# Test passing parameters as None when temperature_good == False
psu_status.temperature_good = False
ret = psu_status.set_temperature(None, 50.0)
assert ret == False
assert psu_status.temperature_good == True
psu_status.temperature_good = False
ret = psu_status.set_temperature(20.123, None)
assert ret == False
assert psu_status.temperature_good == True

def test_is_ok(self):
mock_logger = MagicMock()
mock_psu = MockPsu(True, True, "PSU 1")

psu_status = PsuStatus(mock_logger, mock_psu)
psu_status.presence = True
psu_status.power_good = True
psu_status.voltage_good = True
psu_status.temperature_good = True
ret = psu_status.is_ok()
assert ret == True

psu_status.presence = False
ret = psu_status.is_ok()
assert ret == False

psu_status.presence = True
ret = psu_status.is_ok()
assert ret == True

psu_status.power_good = False
ret = psu_status.is_ok()
assert ret == False

psu_status.power_good = True
ret = psu_status.is_ok()
assert ret == True

psu_status.voltage_good = False
ret = psu_status.is_ok()
assert ret == False

psu_status.voltage_good = True
ret = psu_status.is_ok()
assert ret == True

psu_status.temperature_good = False
ret = psu_status.is_ok()
assert ret == False

psu_status.temperature_good = True
ret = psu_status.is_ok()
assert ret == True
90 changes: 89 additions & 1 deletion sonic-psud/tests/test_psud.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
import sys
from imp import load_source

from mock import Mock, MagicMock, patch
# TODO: Clean this up once we no longer need to support Python 2
if sys.version_info.major == 3:
from unittest.mock import Mock, MagicMock, patch
else:
from mock import Mock, MagicMock, patch
from sonic_py_common import daemon_base

from .mock_platform import MockChassis, MockPsu, MockFanDrawer, MockModule
Expand Down Expand Up @@ -169,3 +173,87 @@ def test_psuchassis_check_power_budget():
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


def test_get_psu_key():
assert get_psu_key(0) == PSU_INFO_KEY_TEMPLATE.format(0)
assert get_psu_key(1) == PSU_INFO_KEY_TEMPLATE.format(1)


def test_try_get():
# Test a proper, working callback
GOOD_CALLBACK_RETURN_VALUE = "This is a test"

def callback1():
return GOOD_CALLBACK_RETURN_VALUE

ret = try_get(callback1)
assert ret == GOOD_CALLBACK_RETURN_VALUE

# Ensure try_get returns default value if callback returns None
DEFAULT_VALUE = "Default value"

def callback2():
return None

ret = try_get(callback2, default=DEFAULT_VALUE)
assert ret == DEFAULT_VALUE

# Ensure try_get returns default value if callback returns None
def callback3():
raise NotImplementedError

ret = try_get(callback3, default=DEFAULT_VALUE)
assert ret == DEFAULT_VALUE


class TestDaemonPsud(object):
"""
Test cases to cover functionality in DaemonPsud class
"""

def test_set_psu_led(self):
mock_logger = MagicMock()
mock_psu = MockPsu(True, True, "PSU 1")
psu_status = PsuStatus(mock_logger, mock_psu)

daemon_psud = DaemonPsud(SYSLOG_IDENTIFIER)

psu_status.presence = True
psu_status.power_good = True
psu_status.voltage_good = True
psu_status.temperature_good = True
daemon_psud._set_psu_led(mock_psu, psu_status)
assert mock_psu.get_status_led() == mock_psu.STATUS_LED_COLOR_GREEN

psu_status.presence = False
daemon_psud._set_psu_led(mock_psu, psu_status)
assert mock_psu.get_status_led() == mock_psu.STATUS_LED_COLOR_RED

psu_status.presence = True
daemon_psud._set_psu_led(mock_psu, psu_status)
assert mock_psu.get_status_led() == mock_psu.STATUS_LED_COLOR_GREEN

psu_status.power_good = False
daemon_psud._set_psu_led(mock_psu, psu_status)
assert mock_psu.get_status_led() == mock_psu.STATUS_LED_COLOR_RED

psu_status.power_good = True
daemon_psud._set_psu_led(mock_psu, psu_status)
assert mock_psu.get_status_led() == mock_psu.STATUS_LED_COLOR_GREEN

psu_status.voltage_good = False
daemon_psud._set_psu_led(mock_psu, psu_status)
assert mock_psu.get_status_led() == mock_psu.STATUS_LED_COLOR_RED

psu_status.voltage_good = True
daemon_psud._set_psu_led(mock_psu, psu_status)
assert mock_psu.get_status_led() == mock_psu.STATUS_LED_COLOR_GREEN

psu_status.temperature_good = False
daemon_psud._set_psu_led(mock_psu, psu_status)
assert mock_psu.get_status_led() == mock_psu.STATUS_LED_COLOR_RED

psu_status.temperature_good = True
daemon_psud._set_psu_led(mock_psu, psu_status)
assert mock_psu.get_status_led() == mock_psu.STATUS_LED_COLOR_GREEN

0 comments on commit ad0a5ad

Please sign in to comment.