diff --git a/tests/platform_tests/cli/__init__.py b/tests/platform_tests/cli/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/platform_tests/cli/test_show_platform.py b/tests/platform_tests/cli/test_show_platform.py new file mode 100644 index 00000000000..d2f83b41fac --- /dev/null +++ b/tests/platform_tests/cli/test_show_platform.py @@ -0,0 +1,230 @@ +""" +Tests for the `show platform ...` commands in SONiC +""" + +# TODO: All `show` commands should be tested by running as a read-only user. +# This will help catch any permissions issues which may exist. + +# TODO: Add tests for `show platform psustatus ` +# TODO: Add tests for `show platform firmware updates` +# TODO: Add tests for `show platform firmware version` + +import logging +import re +import time + +import pytest + +import util +from tests.common.helpers.assertions import pytest_assert + +pytestmark = [ + pytest.mark.sanity_check(skip_sanity=True), + pytest.mark.disable_loganalyzer, # disable automatic loganalyzer + pytest.mark.topology('any') +] + +CMD_SHOW_PLATFORM = "show platform" + +THERMAL_CONTROL_TEST_WAIT_TIME = 65 +THERMAL_CONTROL_TEST_CHECK_INTERVAL = 5 + + +def test_show_platform_summary(duthost): + """ + @summary: Verify output of `show platform summary` + """ + cmd = " ".join([CMD_SHOW_PLATFORM, "summary"]) + + logging.info("Verifying output of '{}' ...".format(cmd)) + summary_output_lines = duthost.command(cmd)["stdout_lines"] + summary_dict = util.parse_colon_speparated_lines(summary_output_lines) + expected_fields = set(["Platform", "HwSKU", "ASIC"]) + actual_fields = set(summary_dict.keys()) + + missing_fields = expected_fields - actual_fields + pytest_assert(len(missing_fields) == 0, "Output missing fields: {}".format(repr(missing_fields))) + + unexpected_fields = actual_fields - expected_fields + pytest_assert(len(unexpected_fields) == 0, "Unexpected fields in output: {}".format(repr(unexpected_fields))) + + # TODO: Test values against platform-specific expected data instead of testing for missing values + for key in expected_fields: + pytest_assert(summary_dict[key], "Missing value for '{}'".format(key)) + + +def test_show_platform_syseeprom(duthost): + """ + @summary: Verify output of `show platform syseeprom` + """ + cmd = " ".join([CMD_SHOW_PLATFORM, "syseeprom"]) + + logging.info("Verifying output of '{}' ...".format(cmd)) + syseeprom_output = duthost.command(cmd)["stdout"] + # TODO: Gather expected data from a platform-specific data file instead of this method + + if duthost.facts["asic_type"] in ["mellanox"]: + expected_fields = [ + "Product Name", + "Part Number", + "Serial Number", + "Base MAC Address", + "Manufacture Date", + "Device Version", + "MAC Addresses", + "Manufacturer", + "Vendor Extension", + "ONIE Version", + "CRC-32"] + + utility_cmd = "sudo python -c \"import imp; \ + m = imp.load_source('eeprom', '/usr/share/sonic/device/%s/plugins/eeprom.py'); \ + t = m.board('board', '', '', ''); e = t.read_eeprom(); t.decode_eeprom(e)\"" % duthost.facts["platform"] + + utility_cmd_output = duthost.command(utility_cmd) + + for field in expected_fields: + pytest_assert(syseeprom_output.find(field) >= 0, "Expected field '{}' was not found".format(field)) + pytest_assert(utility_cmd_output["stdout"].find(field) >= 0, "Expected field '{}' was not found".format(field)) + + for line in utility_cmd_output["stdout_lines"]: + pytest_assert(line in syseeprom_output, "Line '{}' was not found in output".format(line)) + + +# TODO: Gather expected data from a platform-specific data file instead of this method +def check_vendor_specific_psustatus(dut, psu_status_line): + """ + @summary: Vendor specific psu status check + """ + if dut.facts["asic_type"] in ["mellanox"]: + from ..mellanox.check_sysfs import check_psu_sysfs + + psu_line_pattern = re.compile(r"PSU\s+(\d)+\s+(OK|NOT OK|NOT PRESENT)") + psu_match = psu_line_pattern.match(psu_status_line) + psu_id = psu_match.group(1) + psu_status = psu_match.group(2) + + check_psu_sysfs(dut, psu_id, psu_status) + + +def test_show_platform_psustatus(duthost): + """ + @summary: Verify output of `show platform psustatus` + """ + cmd = " ".join([CMD_SHOW_PLATFORM, "psustatus"]) + + logging.info("Verifying output of '{}' ...".format(cmd)) + psu_status_output_lines = duthost.command(cmd)["stdout_lines"] + psu_line_pattern = re.compile(r"PSU\s+\d+\s+(OK|NOT OK|NOT PRESENT)") + for line in psu_status_output_lines[2:]: + pytest_assert(psu_line_pattern.match(line), "Unexpected PSU status output: '{}'".format(line)) + check_vendor_specific_psustatus(duthost, line) + + +def verify_show_platform_fan_output(raw_output_lines): + """ + @summary: Verify output of `show platform fan`. Expected output is + "Fan Not detected" or a table of fan status data conaining 8 columns. + """ + NUM_EXPECTED_COLS = 8 + + pytest_assert(len(raw_output_lines) > 0, "There must be at least one line of output") + if len(raw_output_lines) == 1: + pytest_assert(raw_output_lines[0].encode('utf-8').strip() == "Fan Not detected", "Unexpected fan status output") + else: + pytest_assert(len(raw_output_lines) > 2, "There must be at least two lines of output if any fan is detected") + second_line = raw_output_lines[1] + field_ranges = util.get_field_range(second_line) + pytest_assert(len(field_ranges) == NUM_EXPECTED_COLS, "Output should consist of {} columns".format(NUM_EXPECTED_COLS)) + + +def test_show_platform_fan(duthost): + """ + @summary: Verify output of `show platform fan` + """ + cmd = " ".join([CMD_SHOW_PLATFORM, "fan"]) + + logging.info("Verifying output of '{}' ...".format(cmd)) + fan_status_output_lines = duthost.command(cmd)["stdout_lines"] + verify_show_platform_fan_output(fan_status_output_lines) + + # TODO: Test values against platform-specific expected data + + +def verify_show_platform_temperature_output(raw_output_lines): + """ + @summary: Verify output of `show platform temperature`. Expected output is + "Thermal Not detected" or a table of thermal status data with 8 columns. + """ + NUM_EXPECTED_COLS = 8 + + pytest_assert(len(raw_output_lines) > 0, "There must be at least one line of output") + if len(raw_output_lines) == 1: + pytest_assert(raw_output_lines[0].encode('utf-8').strip() == "Thernal Not detected", "Unexpected thermal status output") + else: + pytest_assert(len(raw_output_lines) > 2, "There must be at least two lines of output if any thermal is detected") + second_line = raw_output_lines[1] + field_ranges = util.get_field_range(second_line) + pytest_assert(len(field_ranges) == NUM_EXPECTED_COLS, "Output should consist of {} columns".format(NUM_EXPECTED_COLS)) + + +def test_show_platform_temperature(duthost): + """ + @summary: Verify output of `show platform temperature` + """ + cmd = " ".join([CMD_SHOW_PLATFORM, "temperature"]) + + logging.info("Verifying output of '{}' ...".format(cmd)) + temperature_output_lines = duthost.command(cmd)["stdout_lines"] + verify_show_platform_temperature_output(temperature_output_lines) + + # TODO: Test values against platform-specific expected data + + +def test_show_platform_ssdhealth(duthost): + """ + @summary: Verify output of `show platform ssdhealth` + """ + cmd = " ".join([CMD_SHOW_PLATFORM, "ssdhealth"]) + + logging.info("Verifying output of '{}' ...".format(cmd)) + ssdhealth_output_lines = duthost.command(cmd)["stdout_lines"] + ssdhealth_dict = util.parse_colon_speparated_lines(ssdhealth_output_lines) + expected_fields = set(["Device Model", "Health", "Temperature"]) + actual_fields = set(ssdhealth_dict.keys()) + + missing_fields = expected_fields - actual_fields + pytest_assert(len(missing_fields) == 0, "Output missing fields: {}".format(repr(missing_fields))) + + unexpected_fields = actual_fields - expected_fields + pytest_assert(len(unexpected_fields) == 0, "Unexpected fields in output: {}".format(repr(unexpected_fields))) + + # TODO: Test values against platform-specific expected data instead of testing for missing values + for key in expected_fields: + pytest_assert(ssdhealth_dict[key], "Missing value for '{}'".format(key)) + + +def verify_show_platform_firmware_status_output(raw_output_lines): + """ + @summary: Verify output of `show platform firmware status`. Expected output is + a table of firmware data conaining 5 columns. + """ + NUM_EXPECTED_COLS = 5 + + pytest_assert(len(raw_output_lines) > 2, "There must be at least two lines of output") + second_line = raw_output_lines[1] + field_ranges = util.get_field_range(second_line) + pytest_assert(len(field_ranges) == NUM_EXPECTED_COLS, "Output should consist of {} columns".format(NUM_EXPECTED_COLS)) + + +def test_show_platform_firmware_status(duthost): + """ + @summary: Verify output of `show platform firmware status` + """ + cmd = " ".join([CMD_SHOW_PLATFORM, "firmware", "status"]) + + logging.info("Verifying output of '{}' ...".format(cmd)) + firmware_output_lines = duthost.command(cmd)["stdout_lines"] + verify_show_platform_firmware_status_output(firmware_output_lines) + + # TODO: Test values against platform-specific expected data diff --git a/tests/platform_tests/cli/util.py b/tests/platform_tests/cli/util.py new file mode 100644 index 00000000000..44210d91e5c --- /dev/null +++ b/tests/platform_tests/cli/util.py @@ -0,0 +1,64 @@ +""" +util.py + +Utility functions for testing SONiC CLI +""" + + +def parse_colon_speparated_lines(lines): + """ + @summary: Helper function for parsing lines which consist of key-value pairs + formatted like ": ", where the colon can be surrounded + by 0 or more whitespace characters + @return: A dictionary containing key-value pairs of the output + """ + res = {} + for line in lines: + fields = line.split(":") + if len(fields) != 2: + continue + res[fields[0].strip()] = fields[1].strip() + return res + + +def get_field_range(second_line): + """ + @summary: Utility function to help get field range from a simple tabulate output line. + Simple tabulate output looks like: + + Head1 Head2 H3 H4 + ----- ------ ------- -- + V1 V2 V3 V4 + + @return: Returned a list of field range. E.g. [(0,4), (6, 10)] means there are two fields for + each line, the first field is between position 0 and position 4, the second field is between + position 6 and position 10. + """ + field_ranges = [] + begin = 0 + while 1: + end = second_line.find(' ', begin) + if end == -1: + field_ranges.append((begin, len(second_line))) + break + + field_ranges.append((begin, end)) + begin = second_line.find('-', end) + if begin == -1: + break + + return field_ranges + + +def get_fields(line, field_ranges): + """ + @summary: Utility function to help extract all fields from a simple tabulate output line + based on field ranges got from function get_field_range. + @return: A list of fields. + """ + fields = [] + for field_range in field_ranges: + field = line[field_range[0]:field_range[1]].encode('utf-8') + fields.append(field.strip()) + + return fields diff --git a/tests/platform_tests/test_platform_info.py b/tests/platform_tests/test_platform_info.py index f941db6a8b5..0e49adc541f 100644 --- a/tests/platform_tests/test_platform_info.py +++ b/tests/platform_tests/test_platform_info.py @@ -18,7 +18,6 @@ pytest.mark.topology('any') ] -CMD_PLATFORM_SUMMARY = "show platform summary" CMD_PLATFORM_PSUSTATUS = "show platform psustatus" CMD_PLATFORM_FANSTATUS = "show platform fan" CMD_PLATFORM_TEMPER = "show platform temperature" @@ -103,50 +102,6 @@ def psu_test_setup_teardown(duthost): logging.info("sensord is running, pid = {}".format(sensord_pid)) -def test_show_platform_summary(duthost): - """ - @summary: Check output of 'show platform summary' - """ - logging.info("Check output of '%s'" % CMD_PLATFORM_SUMMARY) - platform_summary = duthost.command(CMD_PLATFORM_SUMMARY) - expected_fields = set(["Platform", "HwSKU", "ASIC"]) - actual_fields = set() - for line in platform_summary["stdout_lines"]: - key_value = line.split(":") - assert len(key_value) == 2, "output format is not 'field_name: field_value'" - assert len(key_value[1]) > 0, "No value for field %s" % key_value[0] - actual_fields.add(line.split(":")[0]) - assert actual_fields == expected_fields, \ - "Unexpected output fields, actual=%s, expected=%s" % (str(actual_fields), str(expected_fields)) - - -def check_vendor_specific_psustatus(dut, psu_status_line): - """ - @summary: Vendor specific psu status check - """ - if dut.facts["asic_type"] in ["mellanox"]: - from .mellanox.check_sysfs import check_psu_sysfs - - psu_line_pattern = re.compile(r"PSU\s+(\d)+\s+(OK|NOT OK|NOT PRESENT)") - psu_match = psu_line_pattern.match(psu_status_line) - psu_id = psu_match.group(1) - psu_status = psu_match.group(2) - - check_psu_sysfs(dut, psu_id, psu_status) - - -def test_show_platform_psustatus(duthost): - """ - @summary: Check output of 'show platform psustatus' - """ - logging.info("Check PSU status using '%s', hostname: %s" % (CMD_PLATFORM_PSUSTATUS, duthost.hostname)) - psu_status = duthost.command(CMD_PLATFORM_PSUSTATUS) - psu_line_pattern = re.compile(r"PSU\s+\d+\s+(OK|NOT OK|NOT PRESENT)") - for line in psu_status["stdout_lines"][2:]: - assert psu_line_pattern.match(line), "Unexpected PSU status output" - check_vendor_specific_psustatus(duthost, line) - - def get_psu_num(dut): cmd_num_psu = "sudo psuutil numpsus" @@ -256,45 +211,10 @@ def test_turn_on_off_psu_and_check_psustatus(duthost, psu_controller): loganalyzer.analyze(marker) -def parse_platform_summary(raw_input_lines): - """ - @summary: Helper function for parsing the output of 'show system platform' - @return: Returned parsed information in a dictionary - """ - res = {} - for line in raw_input_lines: - fields = line.split(":") - if len(fields) != 2: - continue - res[fields[0].lower()] = fields[1].strip() - return res - - -def check_show_platform_fanstatus_output(lines): - """ - @summary: Check basic output of 'show platform fan'. Expect output are: - "Fan Not detected" or a table of fan status data with 8 columns. - """ - assert len(lines) > 0, 'There must be at least one line output for show platform fans' - if len(lines) == 1: - assert lines[0].encode('utf-8').strip() == 'Fan Not detected' - else: - assert len(lines) > 2, 'There must be at least two lines output for show platform fans if any FAN is detected' - second_line = lines[1] - field_ranges = get_field_range(second_line) - assert len(field_ranges) == 8, 'There must be 8 columns in output of show platform fans' - - -def test_show_platform_fanstatus(duthost, mocker_factory): +def test_show_platform_fanstatus_mocked(duthost, mocker_factory): """ @summary: Check output of 'show platform fan'. """ - # Do basic check first - logging.info("Check output of '%s'" % CMD_PLATFORM_FANSTATUS) - cli_fan_status = duthost.command(CMD_PLATFORM_FANSTATUS) - lines = cli_fan_status["stdout_lines"] - check_show_platform_fanstatus_output(lines) - # Mock data and check mocker = mocker_factory(duthost, 'FanStatusMocker') if mocker is None: @@ -308,29 +228,10 @@ def test_show_platform_fanstatus(duthost, mocker_factory): assert result, 'FAN mock data mismatch' -def check_show_platform_temperature_output(lines): - """ - @summary: Check basic output of 'show platform temperature'. Expect output are: - "Thermal Not detected" or a table of thermal status data with 8 columns. - """ - assert len(lines) > 0, 'There must be at least one line output for show platform temperature' - if len(lines) == 1: - assert lines[0].encode('utf-8').strip() == 'Thermal Not detected' - else: - assert len(lines) > 2, 'There must be at least two lines output for show platform temperature if any thermal is detected' - second_line = lines[1] - field_ranges = get_field_range(second_line) - assert len(field_ranges) == 8, 'There must be 8 columns in output of show platform temperature' - - -def test_show_platform_temperature(duthost, mocker_factory): +def test_show_platform_temperature_mocked(duthost, mocker_factory): """ @summary: Check output of 'show platform temperature' """ - # Do basic check first - logging.info("Check output of '%s'" % CMD_PLATFORM_TEMPER) - cli_thermal_status = duthost.command(CMD_PLATFORM_TEMPER) - # Mock data and check mocker = mocker_factory(duthost, 'ThermalStatusMocker') if mocker is None: diff --git a/tests/platform_tests/test_syseeprom.py b/tests/platform_tests/test_syseeprom.py deleted file mode 100644 index 87c240f548a..00000000000 --- a/tests/platform_tests/test_syseeprom.py +++ /dev/null @@ -1,47 +0,0 @@ -""" -Tests to ensure we can properly query and parse data from the system EEPROM of the device -""" - -import logging - -import pytest - - -CMD_PLATFORM_SYSEEPROM = "show platform syseeprom" - -pytestmark = [ - pytest.mark.disable_loganalyzer, # disable automatic loganalyzer - pytest.mark.topology('any') -] - -def test_show_platform_syseeprom(duthost): - """ - @summary: Check output of 'show platform syseeprom' - """ - logging.info("Check output of '%s'" % CMD_PLATFORM_SYSEEPROM) - show_output = duthost.command(CMD_PLATFORM_SYSEEPROM) - if duthost.facts["asic_type"] in ["mellanox"]: - expected_fields = [ - "Product Name", - "Part Number", - "Serial Number", - "Base MAC Address", - "Manufacture Date", - "Device Version", - "MAC Addresses", - "Manufacturer", - "Vendor Extension", - "ONIE Version", - "CRC-32"] - utility_cmd = "sudo python -c \"import imp; \ - m = imp.load_source('eeprom', '/usr/share/sonic/device/%s/plugins/eeprom.py'); \ - t = m.board('board', '', '', ''); e = t.read_eeprom(); t.decode_eeprom(e)\"" % duthost.facts["platform"] - utility_cmd_output = duthost.command(utility_cmd) - - for field in expected_fields: - assert show_output["stdout"].find(field) >= 0, "Expected field %s is not found" % field - assert utility_cmd_output["stdout"].find(field) >= 0, "Expected field %s is not found" % field - - for line in utility_cmd_output["stdout_lines"]: - assert line in show_output["stdout"], \ - "Line %s is not found in output of '%s'" % (line, CMD_PLATFORM_SYSEEPROM)