diff --git a/ansible/roles/test/tasks/portstat.yml b/ansible/roles/test/tasks/portstat.yml index 0ade4fe5b32..8c731cfb9b1 100644 --- a/ansible/roles/test/tasks/portstat.yml +++ b/ansible/roles/test/tasks/portstat.yml @@ -1,79 +1,4 @@ -- name: Verify the portstat clear command works - # roles/test/tasks/portstat - include_tasks: portstat/portstat_clear.yml +- name: run test + include_tasks: roles/test/tasks/pytest_runner.yml vars: - command: "{{ item }}" - with_items: - - portstat -c - - portstat --clear - -- name: Test portstat delete all when no saved data # This command fails - include_tasks: portstat/portstat_delete_all_no_saved_stats.yml - vars: - command: "{{ item }}" - with_items: - - portstat -D - - portstat --delete-all - -- name: Test portstat delete all with saved tags - include_tasks: portstat/portstat_delete_all_stats.yml - vars: - command: "{{ item }}" - with_items: - - portstat -D - - portstat --delete-all - -- name: Test deleting a tag - include_tasks: portstat/portstat_delete_tag.yml - vars: - command: "{{ item }}" - with_items: - - portstat -d -t - - portstat -d --tag - - portstat --delete -t - - portstat --delete --tag - -- name: Test display all - include_tasks: portstat/portstat_all_fields.yml - vars: - command: "{{ item }}" - with_items: - - portstat -a - - portstat --all - -- name: Test display period - include_tasks: portstat/portstat_period.yml - vars: - command: "{{ item }}" - with_items: - - portstat -p 1 - - portstat --period 1 - - -# This test verifies the following command versions don't cause tracebacks -- block: - - name: list of commands to run - set_fact: - portstat_commands: - - portstat -h - - portstat --help - - portstat - - portstat -v - - portstat --version - - portstat -j #This test fails - - portstat --json #This test fails - - portstat -r #This test fails - - portstat --raw #This test fails - - - name: Run the commands - shell: "{{ item }}" - with_items: "{{ portstat_commands }}" - - always: - - name: Clear and reset counters - shell: portstat -D - - rescue: - - name: A command failed - debug: - msg: One of the commands failed + test_node: portstat/test_portstat.py diff --git a/ansible/roles/test/tasks/portstat/portstat_all_fields.yml b/ansible/roles/test/tasks/portstat/portstat_all_fields.yml deleted file mode 100644 index 543e33e4df8..00000000000 --- a/ansible/roles/test/tasks/portstat/portstat_all_fields.yml +++ /dev/null @@ -1,41 +0,0 @@ -- block: - # Makes sure all tags are cleared out - - name: Clear out all tags - shell: portstat -D - ignore_errors: True - - - name: run the base command - shell: portstat - register: base_out - - - name: pull out the number of columns from the headers - set_fact: - top_row_base_number_of_items: "{{ base_out.stdout_lines[0].split()|length }}" - - - name: run the all items command - shell: "{{ command }}" - register: all_out - - - name: pull out the number of columns from the headers - set_fact: - top_row_all_number_of_items: "{{ all_out.stdout_lines[0].split()|length }}" - - - name: verify the all number of columns is greater than the base number of columns - assert: - that: top_row_all_number_of_items > top_row_base_number_of_items - - rescue: - - debug: - msg: "A failure occured" - - - debug: - var: top_row_base_number_of_items - - - debug: - var: top_row_all_number_of_items - - always: - - name: reset portstat command - shell: portstat -D - ignore_errors: True - diff --git a/ansible/roles/test/tasks/portstat/portstat_clear.yml b/ansible/roles/test/tasks/portstat/portstat_clear.yml deleted file mode 100644 index db32c84ec39..00000000000 --- a/ansible/roles/test/tasks/portstat/portstat_clear.yml +++ /dev/null @@ -1,64 +0,0 @@ -- block: - # Makes sure all tags are cleared out - - name: Clear out all tags - shell: portstat -D - ignore_errors: True - - # This test verifies if the portstat -c command works as expected - - name: pull current portstat - shell: portstat | grep Ethernet0 - register: before_out - - - name: Find ethernet0 - set_fact: - portstat_eth0: "{{ item }}" - with_items: "{{ before_out.stdout_lines }}" - when: "'Ethernet0' in item" - - - name: Pull out RX and TX OK counters - set_fact: - before_rx_ok: "{{ portstat_eth0.split()[2].replace(',','') }}" - before_tx_ok: "{{ portstat_eth0.split()[9].replace(',','') }}" - - # This is the test command - - name: Testing '{{ command }}' - shell: "{{ command }}" - - - name: wait a few seconds for data to repopulate - pause: - seconds: 5 - - - name: pull current portstat - shell: portstat | grep Ethernet0 - register: after_out - - - name: Find ethernet0 - set_fact: - portstat_eth0: "{{ item }}" - with_items: "{{ after_out.stdout_lines }}" - when: "'Ethernet0' in item" - - - name: Pull out RX and TX OK counters - set_fact: - after_rx_ok: "{{ portstat_eth0.split()[2].replace(',','') }}" - after_tx_ok: "{{ portstat_eth0.split()[9].replace(',','') }}" - - - name: Assert that the cleared rx counter is less than the current counter - assert: - that: "{{ before_rx_ok|int }} > {{ after_rx_ok|int }}" - msg: "'before_rx_ok: {{ before_rx_ok }}' is not greater than 'after_rx_ok: {{ after_rx_ok }}'" - - - name: Assert that the cleared tx counter is less than the current counter - assert: - that: "{{ before_tx_ok|int }} > {{ after_tx_ok|int }}" - msg: "'before_tx_ok: {{ before_tx_ok }}' is not greater than 'after_tx_ok: {{ after_tx_ok }}'" - - rescue: - - debug: - msg: "A failure occured" - - always: - - name: reset portstat command - shell: portstat -D - ignore_errors: True - diff --git a/ansible/roles/test/tasks/portstat/portstat_delete_all_no_saved_stats.yml b/ansible/roles/test/tasks/portstat/portstat_delete_all_no_saved_stats.yml deleted file mode 100644 index 57a10d82495..00000000000 --- a/ansible/roles/test/tasks/portstat/portstat_delete_all_no_saved_stats.yml +++ /dev/null @@ -1,23 +0,0 @@ -- block: - # This test runs the delete all command - # This test fails as of the writing of this test - - # Makes sure all tags are cleared out - - name: Clear out all tags - shell: portstat -D - ignore_errors: True - - - name: Clear out all tags using the command '{{ command }}' #This test fails - shell: "{{ command }}" - register: out - - rescue: - - name: Display output on failure - debug: - var: out - - always: - - name: Clear out all tags - shell: portstat -D - ignore_errors: True - diff --git a/ansible/roles/test/tasks/portstat/portstat_delete_all_stats.yml b/ansible/roles/test/tasks/portstat/portstat_delete_all_stats.yml deleted file mode 100644 index 69df1024591..00000000000 --- a/ansible/roles/test/tasks/portstat/portstat_delete_all_stats.yml +++ /dev/null @@ -1,50 +0,0 @@ -- block: - # This command adds some tags, verifies they exist, then deletes them all - - # Makes sure all tags are cleared out - - name: Clear out all tags - shell: portstat -D - ignore_errors: True - - - name: create the test file names - set_fact: - file_names: - - test_1 - - test_2 - - test_test - - - name: create several test stats files - shell: portstat -c -t {{ item }} - with_items: "{{ file_names }}" - - - name: pull the list of objects out of the directory - shell: ls -R /tmp - register: ls_out_before - - - name: verify that the file names are in the directory - assert: - that: ("1000-" + item) in ls_out_before.stdout - with_items: "{{ file_names }}" - - - name: run the command to be tested '{{ command }}' - shell: "{{ command }}" - - - name: pull the list of objects out of the directory - shell: ls -R /tmp - register: ls_out_after - - - name: verify that the file names are not the directory - assert: - that: ("1000-" + item) not in ls_out_after.stdout - with_items: "{{ file_names }}" - - rescue: - - name: An error occured - debug: - msg: An error occured - - always: - - name: Clear out all tags - shell: portstat -D - ignore_errors: True - diff --git a/ansible/roles/test/tasks/portstat/portstat_delete_tag.yml b/ansible/roles/test/tasks/portstat/portstat_delete_tag.yml deleted file mode 100644 index 51d3dbcea0e..00000000000 --- a/ansible/roles/test/tasks/portstat/portstat_delete_tag.yml +++ /dev/null @@ -1,69 +0,0 @@ -- block: - # This command adds some tags, deletes one, verify it's gone but the others exist - - # Makes sure all tags are cleared out - - name: Clear out all tags - shell: portstat -D - ignore_errors: True - - - name: create the test file names - set_fact: - file_names: - - test_1 - - test_2 - - test_delete_me - - - name: create the file to be deleted - set_fact: - file_to_delete: "{{ file_names[2] }}" - - - name: set the list of files to delete to a default value - set_fact: - files_not_deleted: [] - - - name: create the list of files to not be deleted - set_fact: - files_not_deleted: "{{files_not_deleted}} + ['{{item}}']" - with_items: "{{ file_names }}" - when: file_to_delete != item - - - name: create several test stats files - shell: portstat -c -t {{ item }} - with_items: "{{ file_names }}" - - - name: pull the list of objects out of the directory - shell: ls -R /tmp - register: ls_out_before - - - name: verify that the file names are in the directory - assert: - that: ("1000-" + item) in ls_out_before.stdout - with_items: "{{ file_names }}" - - - name: run the command to be tested '{{ command }}' - shell: "{{ command }} {{ file_to_delete }}" - - - name: pull the list of objects out of the directory - shell: ls -R /tmp - register: ls_out_after - - - name: verify that the deleted file name is not in the directory - assert: - that: ("1000-" + file_to_delete) not in ls_out_after.stdout - - - name: verify that the remaining file names are in the directory - assert: - that: ("1000-" + item) in ls_out_after.stdout - with_items: "{{ files_not_deleted }}" - - rescue: - - name: An error occured - debug: - msg: An error occured - - always: - - name: Clear out all tags - shell: portstat -D - ignore_errors: True - - diff --git a/ansible/roles/test/tasks/portstat/portstat_period.yml b/ansible/roles/test/tasks/portstat/portstat_period.yml deleted file mode 100644 index d14ee3c6747..00000000000 --- a/ansible/roles/test/tasks/portstat/portstat_period.yml +++ /dev/null @@ -1,39 +0,0 @@ -- block: - # Makes sure all tags are cleared out - - name: Clear out all tags - shell: portstat -D - ignore_errors: True - - - name: set the string to look for - set_fact: - to_find: The rates are calculated within 1 seconds period - - - name: run the command - shell: "{{ command }}" - register: out - - - name: output to verify - set_fact: - found: "{{ out.stdout_lines[0] }}" - - - name: Verify the contents of the first line match the expected string - assert: - that: found == to_find - - rescue: - - debug: - msg: "A failure occured" - - - name: expected output - debug: - var: to_find - - - name: actual output - debug: - var: found - - always: - - name: reset portstat command - shell: portstat -D - ignore_errors: True - diff --git a/tests/portstat/test_portstat.py b/tests/portstat/test_portstat.py new file mode 100644 index 00000000000..96d788abcb2 --- /dev/null +++ b/tests/portstat/test_portstat.py @@ -0,0 +1,197 @@ + +import logging +import pytest + +from common.helpers.assertions import pytest_assert +from common.utilities import wait + +logger = logging.getLogger('__name__') + + +def parse_column_positions(separation_line, separation_char='-'): + '''Parse the position of each columns in the command output + + Args: + separation_line (string): The output line separating actual data and column headers + separation_char (str, optional): The character used in separation line. Defaults to '-'. + + Returns: + [list]: A list. Each item is a tuple with two elements. The first element is start position of a column. The + second element is the end position of the column. + ''' + prev = ' ', + positions = [] + for pos, char in enumerate(separation_line + ' '): + if char == separation_char: + if char != prev: + left = pos + else: + if char != prev: + right = pos + positions.append((left, right)) + prev = char + return positions + + +def parse_portstat(content_lines): + '''Parse the output of portstat command + + Args: + content_lines (list): The output lines of portstat command + + Returns: + list: A dictionary, key is interface name, value is a dictionary of fields/values + ''' + + header_line = '' + separation_line = '' + separation_line_number = 0 + for idx, line in enumerate(content_lines): + if line.find('----') >= 0: + header_line = content_lines[idx-1] + separation_line = content_lines[idx] + separation_line_number = idx + break + + try: + positions = parse_column_positions(separation_line) + except Exception: + logger.error('Possibly bad command output') + return {} + + headers = [] + for pos in positions: + header = header_line[pos[0]:pos[1]].strip().lower() + headers.append(header) + + if not headers: + return {} + + results = {} + for line in content_lines[separation_line_number+1:]: + portstats = [] + for pos in positions: + portstat = line[pos[0]:pos[1]].strip() + portstats.append(portstat) + + intf = portstats[0] + results[intf] = {} + for idx in range(1, len(portstats)): # Skip the first column interface name + results[intf][headers[idx]] = portstats[idx] + + return results + + +@pytest.fixture(scope='function', autouse=True) +def reset_portstat(duthost): + logger.info('Clear out all tags') + duthost.command('portstat -D', become=True, module_ignore_errors=True) + + yield + + logger.info("Reset portstate ") + duthost.command('portstat -D', become=True, module_ignore_errors=True) + + +@pytest.mark.parametrize('command', ['portstat -c', 'portstat --clear']) +def test_portstat_clear(duthost, command): + + before_portstat = parse_portstat(duthost.command('portstat')['stdout_lines']) + pytest_assert(before_portstat, 'No parsed command output') + + duthost.command(command) + wait(5, 'Wait for portstat counters to refresh') + + after_portstat = parse_portstat(duthost.command('portstat')['stdout_lines']) + pytest_assert(after_portstat, 'No parsed command output') + + for intf in before_portstat: + pytest_assert(int(before_portstat[intf]['rx_ok']) >= int(after_portstat[intf]['rx_ok']), + 'Value of RX_OK after clear should be lesser') + + pytest_assert(int(before_portstat[intf]['tx_ok']) >= int(after_portstat[intf]['rx_ok']), + 'Value of RX_OK after clear should be lesser') + + +@pytest.mark.parametrize('command', ['portstat -D', 'portstat --delete-all']) +def test_portstat_delete_all(duthost, command): + + stats_files = ('test_1', 'test_2', 'test_test') + + logger.info('Create several test stats files') + for stats_file in stats_files: + duthost.command('portstat -c -t {}'.format(stats_file)) + + logger.info('Verify that the file names are in the /tmp directory') + uid = duthost.command('id -u')['stdout'].strip() + for stats_file in stats_files: + pytest_assert(duthost.stat(path='/tmp/portstat-{uid}/{uid}-{filename}'\ + .format(uid=uid, filename=stats_file))['stat']['exists']) + + logger.info('Run the command to be tested "{}"'.format(command)) + duthost.command(command) + + logger.info('Verify that the file names are not in the /tmp directory') + for stats_file in stats_files: + pytest_assert(not duthost.stat(path='/tmp/portstat-{uid}/{uid}-{filename}'\ + .format(uid=uid, filename=stats_file))['stat']['exists']) + + +@pytest.mark.parametrize('command', + ['portstat -d -t', 'portstat -d --tag', 'portstat --delete -t', 'portstat --delete --tag']) +def test_portstat_delete_tag(duthost, command): + + stats_files = ('test_1', 'test_2', 'test_delete_me') + file_to_delete = stats_files[2] + files_not_deleted = stats_files[:2] + + logger.info('Create several test stats files') + for stats_file in stats_files: + duthost.command('portstat -c -t {}'.format(stats_file)) + + logger.info('Verify that the file names are in the /tmp directory') + uid = duthost.command('id -u')['stdout'].strip() + for stats_file in stats_files: + pytest_assert(duthost.stat(path='/tmp/portstat-{uid}/{uid}-{filename}'\ + .format(uid=uid, filename=stats_file))['stat']['exists']) + + full_delete_command = command + ' ' + file_to_delete + logger.info('Run the command to be tested "{}"'.format(full_delete_command)) + duthost.command(full_delete_command) + + logger.info('Verify that the deleted file name is not in the directory') + pytest_assert(not duthost.stat(path='/tmp/portstat-{uid}/{uid}-{filename}'\ + .format(uid=uid, filename=file_to_delete))['stat']['exists']) + + logger.info('Verify that the remaining file names are in the directory') + for stats_file in files_not_deleted: + pytest_assert(duthost.stat(path='/tmp/portstat-{uid}/{uid}-{filename}'\ + .format(uid=uid, filename=stats_file))['stat']['exists']) + + +@pytest.mark.parametrize('command', ['portstat -a', 'portstat --all']) +def test_portstat_display_all(duthost, command): + + base_portstat = parse_portstat(duthost.command('portstat')['stdout_lines']) + all_portstats = parse_portstat(duthost.command(command)['stdout_lines']) + pytest_assert(base_portstat and all_portstats, 'No parsed command output') + + logger.info('Verify the all number of columns is greater than the base number of columns') + for intf in all_portstats.keys(): + pytest_assert(len(all_portstats[intf].keys()) > len(base_portstat[intf].keys())) + + +@pytest.mark.parametrize('command', ['portstat -p 1', 'portstat --period 1']) +def test_portstat_period(duthost, command): + + output = duthost.command(command) + pytest_assert('The rates are calculated within 1 seconds period' in output['stdout_lines'][0]) + + +@pytest.mark.parametrize('command', ['portstat -h', 'portstat --help', 'portstat', 'portstat -v', + 'portstat --version', 'portstat -j', 'portstat --json', + 'portstat -r', 'portstat --raw']) +def test_portstat_no_exceptions(duthost, command): + + logger.info('Verify that the commands do not cause tracebacks') + duthost.command(command)