Skip to content

Commit

Permalink
[ledd] Minor refactor; add unit tests (sonic-net#143)
Browse files Browse the repository at this point in the history
- Refactor ledd:
    - Remove useless try/catch from around imports
    - Move argument parsing out of `DaemonLedd.run()` method and into `main()` function, a more appropriate location
    - Fix LGTM alert for unreachable code

- Add unit tests and report coverage:
    - Test passing good and bad command-line arguments to ledd process

Unit test coverage with this patch:
```
----------- coverage: platform linux, python 3.7.3-final-0 -----------
Name           Stmts   Miss  Cover
----------------------------------
scripts/ledd      66     34    48%
Coverage HTML written to dir htmlcov
Coverage XML written to file coverage.xml
```
  • Loading branch information
jleveque authored Jan 22, 2021
1 parent d7b9284 commit e72f6cd
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 30 deletions.
2 changes: 2 additions & 0 deletions sonic-ledd/pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[pytest]
addopts = --cov=scripts --cov-report html --cov-report term --cov-report xml
53 changes: 23 additions & 30 deletions sonic-ledd/scripts/ledd
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,13 @@
Front-panel LED control daemon for SONiC
"""

try:
import getopt
import sys
import getopt
import sys

from sonic_py_common import daemon_base
from sonic_py_common import multi_asic
from sonic_py_common.interface import backplane_prefix
from swsscommon import swsscommon
except ImportError as e:
raise ImportError(str(e) + " - required module not found")
from sonic_py_common import daemon_base
from sonic_py_common import multi_asic
from sonic_py_common.interface import backplane_prefix
from swsscommon import swsscommon

#============================= Constants =============================

Expand Down Expand Up @@ -42,25 +39,6 @@ class DaemonLedd(daemon_base.DaemonBase):

# Run daemon
def run(self):
# Parse options if provided
if (len(sys.argv) > 1):
try:
(options, remainder) = getopt.getopt(sys.argv[1:],
'hv',
['help', 'version'])
except getopt.GetoptError as e:
print(e)
print(USAGE_HELP)
sys.exit(2)

for opt, arg in options:
if opt == '--help' or opt == '-h':
print(USAGE_HELP)
sys.exit(0)
elif opt == '--version' or opt == '-v':
print('ledd version ' + VERSION)
sys.exit(0)

# Load platform-specific LedControl module
try:
led_control = self.load_platform_util(LED_MODULE_NAME, LED_CLASS_NAME)
Expand Down Expand Up @@ -117,10 +95,25 @@ class DaemonLedd(daemon_base.DaemonBase):
if not key.startswith(backplane_prefix()):
led_control.port_link_state_change(key, fvp_dict["oper_status"])

return 1


def main():
# Parse options if provided
if len(sys.argv) > 1:
try:
(options, remainder) = getopt.getopt(sys.argv[1:], 'hv', ['help', 'version'])
except getopt.GetoptError as e:
print(e)
print(USAGE_HELP)
sys.exit(1)

for opt, arg in options:
if opt == '--help' or opt == '-h':
print(USAGE_HELP)
sys.exit(0)
elif opt == '--version' or opt == '-v':
print('ledd version {}'.format(VERSION))
sys.exit(0)

ledd = DaemonLedd(SYSLOG_IDENTIFIER)
ledd.run()

Expand Down
2 changes: 2 additions & 0 deletions sonic-ledd/setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[aliases]
test=pytest
5 changes: 5 additions & 0 deletions sonic-ledd/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
setup_requires=[
'wheel'
],
tests_require=[
'mock>=2.0.0; python_version < "3.3"',
'pytest',
'pytest-cov'
],
classifiers=[
'Development Status :: 4 - Beta',
'Environment :: No Input/Output (Daemon)',
Expand Down
Empty file added sonic-ledd/tests/__init__.py
Empty file.
28 changes: 28 additions & 0 deletions sonic-ledd/tests/mock_swsscommon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
STATE_DB = ''


class Table:
def __init__(self, db, table_name):
self.table_name = table_name
self.mock_dict = {}

def _del(self, key):
del self.mock_dict[key]
pass

def set(self, key, fvs):
self.mock_dict[key] = fvs.fv_dict
pass

def get(self, key):
if key in self.mock_dict:
return self.mock_dict[key]
return None


class FieldValuePairs(dict):
def __init__(self, len):
self.fv_dict = {}

def __setitem__(self, key, val_tuple):
self.fv_dict[val_tuple[0]] = val_tuple[1]
68 changes: 68 additions & 0 deletions sonic-ledd/tests/test_ledd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import os
import sys
from imp import load_source

import pytest
# 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

SYSLOG_IDENTIFIER = 'ledd_test'
NOT_AVAILABLE = 'N/A'

daemon_base.db_connect = MagicMock()

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["LEDD_UNIT_TESTING"] = "1"
load_source('ledd', scripts_path + '/ledd')
import ledd


def test_help_args(capsys):
for flag in ['-h', '--help']:
with patch.object(sys, 'argv', ['ledd', flag]):
with pytest.raises(SystemExit) as pytest_wrapped_e:
ledd.main()
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 0
out, err = capsys.readouterr()
assert out.rstrip() == ledd.USAGE_HELP.rstrip()


def test_version_args(capsys):
for flag in ['-v', '--version']:
with patch.object(sys, 'argv', ['ledd', flag]):
with pytest.raises(SystemExit) as pytest_wrapped_e:
ledd.main()
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 0
out, err = capsys.readouterr()
assert out.rstrip() == 'ledd version {}'.format(ledd.VERSION)


def test_bad_args(capsys):
for flag in ['-n', '--nonexistent']:
with patch.object(sys, 'argv', ['ledd', flag]):
with pytest.raises(SystemExit) as pytest_wrapped_e:
ledd.main()
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr()
assert out.rstrip().endswith(ledd.USAGE_HELP.rstrip())


class TestDaemonLedd(object):
"""
Test cases to cover functionality in DaemonLedd class
"""

def test_run(self):
daemon_ledd = ledd.DaemonLedd(SYSLOG_IDENTIFIER)
# TODO: Add more coverage

0 comments on commit e72f6cd

Please sign in to comment.