diff --git a/dist/pythonlibs/riotctrl_shell/congure_test.py b/dist/pythonlibs/riotctrl_shell/congure_test.py new file mode 100644 index 000000000000..5028fc0ae3af --- /dev/null +++ b/dist/pythonlibs/riotctrl_shell/congure_test.py @@ -0,0 +1,111 @@ +# Copyright (C) 2021 Freie Universität Berlin +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. + +""" +`congure_test`-related shell interactions + +Defines `congure_test`-related shell command interactions +""" + +from riotctrl.shell import ShellInteraction + + +class CongureTest(ShellInteraction): + @ShellInteraction.check_term + def setup(self, ident=0, timeout=-1, async_=False): + return self.cmd('cong_setup {ident}'.format(ident=ident), + timeout=timeout, async_=async_) + + @ShellInteraction.check_term + def clear(self, timeout=-1, async_=False): + return self.cmd('cong_clear', timeout=timeout, async_=async_) + + @ShellInteraction.check_term + def init(self, ctx, timeout=-1, async_=False): + return self.cmd('cong_init 0x{ctx:x}'.format(ctx=ctx), + timeout=timeout, async_=async_) + + @ShellInteraction.check_term + def inter_msg_interval(self, msg_size, timeout=-1, async_=False): + return self.cmd('cong_imi {msg_size}'.format(msg_size=msg_size), + timeout=timeout, async_=async_) + + @ShellInteraction.check_term + def add_msg(self, send_time, size, resends, timeout=-1, async_=False): + return self.cmd( + 'cong_add_msg {send_time} {size} {resends}' + .format(send_time=send_time, size=size, resends=resends) + ) + + @ShellInteraction.check_term + def msgs_reset(self, timeout=-1, async_=False): + return self.cmd('cong_msgs_reset') + + @ShellInteraction.check_term + def report(self, cmd, *args, timeout=-1, async_=False): + args = ' '.join(str(a) for a in args) + return self.cmd('cong_report {cmd} {args}'.format(cmd=cmd, args=args)) + + def report_msg_sent(self, msg_size, timeout=-1, async_=False): + return self.report('msg_sent', msg_size, + timeout=timeout, async_=async_) + + def report_msg_discarded(self, msg_size, timeout=-1, async_=False): + return self.report('msg_discarded', msg_size, + timeout=timeout, async_=async_) + + def _report_msgs_timeout_lost_base(self, cmd, timeout=-1, async_=False): + return self.report(cmd, timeout=timeout, async_=async_) + + def _report_msgs_timeout_lost(self, cmd, msgs, timeout=-1, async_=False): + tmp = None + for msg in msgs: + tmp = self.add_msg(**msg) + assert 'success' in tmp + res = self._report_msgs_timeout_lost_base( + cmd, timeout=timeout, async_=async_ + ) + return res + + def report_msgs_timeout_base(self, timeout=-1, async_=False): + return self._report_msgs_timeout_lost_base( + 'msgs_timeout', timeout=timeout, async_=async_ + ) + + def report_msgs_timeout(self, msgs, timeout=-1, async_=False): + return self._report_msgs_timeout_lost( + 'msgs_timeout', msgs, timeout=timeout, async_=async_ + ) + + def report_msgs_lost_base(self, timeout=-1, async_=False): + return self._report_msgs_timeout_lost_base( + 'msgs_lost', timeout=timeout, async_=async_ + ) + + def report_msgs_lost(self, msgs, timeout=-1, async_=False): + return self._report_msgs_timeout_lost( + 'msgs_lost', msgs, timeout=timeout, async_=async_ + ) + + def report_msg_acked_base(self, ack_recv_time, ack_id, ack_size, ack_clean, + ack_wnd, ack_delay, timeout=-1, async_=False): + if isinstance(ack_clean, bool): + ack_clean = int(ack_clean) + return self.report('msg_acked', ack_recv_time, ack_id, ack_size, + ack_clean, ack_wnd, ack_delay, + timeout=-1, async_=False) + + def report_msg_acked(self, msg, ack, timeout=-1, async_=False): + tmp = self.add_msg(**msg) + assert 'success' in tmp + res = self.report_msg_acked_base( + **{'ack_{}'.format(k): v for k, v in ack.items()}, + timeout=timeout, async_=async_ + ) + return res + + def report_ecn_ce(self, time, timeout=-1, async_=False): + return self.report('ecn_ce', time, timeout=timeout, async_=async_) diff --git a/dist/pythonlibs/riotctrl_shell/tests/common.py b/dist/pythonlibs/riotctrl_shell/tests/common.py index 348c90c5b1ef..342fb1be7486 100644 --- a/dist/pythonlibs/riotctrl_shell/tests/common.py +++ b/dist/pythonlibs/riotctrl_shell/tests/common.py @@ -10,17 +10,25 @@ class MockSpawn(): def __init__(self, ctrl, *args, **kwargs): self.ctrl = ctrl - self.last_command = None + self.commands = [] # set some expected attributes self.before = None self.echo = False + @property + def last_command(self): + if self.commands: + return self.commands[-1] + else: + return None + def read_nonblocking(self, size=1, timeout=-1): # do nothing, only used to flush pexpect output pass def sendline(self, line, *args, **kwargs): - self.last_command = line + if line: + self.commands.append(line) if self.ctrl.output is None: # just echo last input for before (what replwrap is assembling # output from) diff --git a/dist/pythonlibs/riotctrl_shell/tests/test_congure_test.py b/dist/pythonlibs/riotctrl_shell/tests/test_congure_test.py new file mode 100644 index 000000000000..a1b630bd5f14 --- /dev/null +++ b/dist/pythonlibs/riotctrl_shell/tests/test_congure_test.py @@ -0,0 +1,130 @@ +# Copyright (C) 2021 Freie Universität Berlin +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. + +import pytest +import riotctrl_shell.congure_test + +from .common import init_ctrl + + +@pytest.fixture +def shell(): + rc = init_ctrl() + yield riotctrl_shell.congure_test.CongureTest(rc) + + +@pytest.fixture +def success_shell(): + rc = init_ctrl() + rc.output = '{"success":null}' + yield riotctrl_shell.congure_test.CongureTest(rc) + + +def test_congure_setup(shell): + res = shell.setup(0) + assert res == 'cong_setup 0' + + +def test_congure_clear(shell): + res = shell.clear() + assert res == 'cong_clear' + + +def test_congure_init(shell): + res = shell.init(0xabcdef) + assert res == 'cong_init 0xabcdef' + + +def test_congure_inter_msg_interval(shell): + res = shell.inter_msg_interval(12345) + assert res == 'cong_imi 12345' + + +def test_congure_add_msg(shell): + res = shell.add_msg(send_time=543234, size=664343, resends=43) + assert res == 'cong_add_msg 543234 664343 43' + + +def test_congure_msgs_reset(shell): + res = shell.msgs_reset() + assert res == 'cong_msgs_reset' + + +def test_congure_report(shell): + res = shell.report('foobar', 'a', 'b', 'c') + assert res == 'cong_report foobar a b c' + + +def test_congure_report_msg_sent(shell): + res = shell.report_msg_sent(12364) + assert res == 'cong_report msg_sent 12364' + + +def test_congure_report_msg_discarded(shell): + res = shell.report_msg_discarded(12364) + assert res == 'cong_report msg_discarded 12364' + + +def test_congure_report_msgs_lost_base(shell): + res = shell.report_msgs_lost_base() + assert res == 'cong_report msgs_lost ' + + +def test_congure_report_msgs_lost(success_shell): + res = success_shell.report_msgs_lost([ + {'send_time': 1545245236, 'size': 12434, 'resends': 32}, + {'send_time': 175344346, 'size': 34323, 'resends': 42}, + ]) + assert res == '{"success":null}' + assert success_shell.riotctrl.term.commands == [ + 'cong_add_msg 1545245236 12434 32', + 'cong_add_msg 175344346 34323 42', + 'cong_report msgs_lost ', + ] + + +def test_congure_report_msgs_timeout_base(shell): + res = shell.report_msgs_timeout_base() + assert res == 'cong_report msgs_timeout ' + + +def test_congure_report_msgs_timeout(success_shell): + res = success_shell.report_msgs_timeout([ + {'send_time': 1545245236, 'size': 12434, 'resends': 32}, + {'send_time': 175344346, 'size': 34323, 'resends': 42}, + ]) + assert res == '{"success":null}' + assert success_shell.riotctrl.term.commands == [ + 'cong_add_msg 1545245236 12434 32', + 'cong_add_msg 175344346 34323 42', + 'cong_report msgs_timeout ', + ] + + +def test_congure_report_msg_acked_base(shell): + res = shell.report_msg_acked_base( + ack_recv_time=12354356, ack_id=4314, ack_size=5632, ack_clean=True, + ack_wnd=45645, ack_delay=12345 + ) + assert res == 'cong_report msg_acked 12354356 4314 5632 1 45645 12345' + + +def test_congure_report_msg_acked(success_shell): + res = success_shell.report_msg_acked( + msg={'send_time': 325242435, 'size': 34321, 'resends': 4}, + ack={'recv_time': 325243543, 'id': 455763452, 'size': 4242, + 'clean': 0, 'wnd': 45645, 'delay': 12345}, + ) + assert res == '{"success":null}' + assert success_shell.riotctrl.term.commands == [ + 'cong_add_msg 325242435 34321 4', + 'cong_report msg_acked 325243543 455763452 4242 0 45645 12345', + ] + + +def test_congure_report_ecn_ce(shell): + res = shell.report_ecn_ce(583424522) + assert res == 'cong_report ecn_ce 583424522' diff --git a/tests/congure_test/README.md b/tests/congure_test/README.md index 656a441c767d..3fa8a2df80da 100644 --- a/tests/congure_test/README.md +++ b/tests/congure_test/README.md @@ -22,7 +22,7 @@ $ BOARD="" make flash test It can also executed with pytest: ```console -$ pytest tests/01-run.py +$ PYTHONPATH=../../dist/pythonlibs BOARD="" pytest tests/01-run.py ``` Note that this only works from within the directory of the test, so if you are diff --git a/tests/congure_test/tests/01-run.py b/tests/congure_test/tests/01-run.py index ea58fc8d9055..5fc8ef090166 100755 --- a/tests/congure_test/tests/01-run.py +++ b/tests/congure_test/tests/01-run.py @@ -15,6 +15,8 @@ from riotctrl.shell import ShellInteraction from riotctrl.shell.json import RapidJSONShellInteractionParser, rapidjson +from riotctrl_shell.congure_test import CongureTest + class TestCongUREBase(unittest.TestCase): DEBUG = False @@ -485,5 +487,165 @@ def test_report_ecn_ce_success(self): time) +class TestCongUREWithCongureTestSI(TestCongUREBase): + @classmethod + def setUpClass(cls): + super(cls, cls).setUpClass() + cls.shell = CongureTest(cls.ctrl) + + def call_method(self, method, *args, timeout=-1, async_=False, **kwargs): + res = getattr(self.shell, method)(*args, + timeout=timeout, async_=async_, + **kwargs) + self.logger.debug(repr(res)) + if res.strip(): + return self.json_parser.parse(res) + return None + + def setUp(self): + super().setUp() + res = self.call_method('setup') + self.congure_state_ptr = int(res['success'], base=16) + + def tearDown(self): + res = self.call_method('msgs_reset') + self.assertIn('success', res) + + def test_init_success(self): + ctx = 0x12345 + res = self.call_method('init', ctx=ctx) + self.assertIsNone(res['success']) + res = self.exec_cmd('state') + self.assertEqual(res['init']['calls'], 1) + self.assertEqual(int(res['init']['last_args']['c'], base=16), + self.congure_state_ptr) + self.assertEqual(int(res['init']['last_args']['ctx'], base=16), + ctx) + + def test_inter_msg_interval_success(self): + msg_size = 521 + res = self.call_method('inter_msg_interval', msg_size=msg_size) + assert res == {'success': -1} + res = self.exec_cmd('state') + self.assertEqual(res['inter_msg_interval']['calls'], 1) + self.assertEqual(int(res['inter_msg_interval']['last_args']['c'], + base=16), + self.congure_state_ptr) + self.assertEqual(res['inter_msg_interval']['last_args']['msg_size'], + msg_size) + + def test_report_unknown_command(self): + res = self.call_method('report', 'foobar') + self.assertEqual(res, {'error': 'Unknown command `foobar`'}) + + def test_report_msg_sent_success(self): + msg_size = 1234 + res = self.call_method('report_msg_sent', msg_size=msg_size) + self.assertIsNone(res['success']) + res = self.exec_cmd('state') + self.assertEqual(res['report_msg_sent']['calls'], 1) + self.assertEqual(int(res['report_msg_sent']['last_args']['c'], + base=16), + self.congure_state_ptr) + self.assertEqual(res['report_msg_sent']['last_args']['msg_size'], + msg_size) + + def test_report_msg_discarded_success(self): + msg_size = 1234 + res = self.call_method('report_msg_discarded', msg_size=msg_size) + self.assertIsNone(res['success']) + res = self.exec_cmd('state') + self.assertEqual(res['report_msg_discarded']['calls'], 1) + self.assertEqual(int(res['report_msg_discarded']['last_args']['c'], + base=16), + self.congure_state_ptr) + self.assertEqual(res['report_msg_discarded']['last_args']['msg_size'], + msg_size) + + def _report_msgs_timeout_lost_base_success(self, cmd): + msgs = [{'send_time': 76543, 'size': 1234, 'resends': 2}, + {'send_time': 5432, 'size': 987, 'resends': 32}] + for msg in msgs: + res = self.call_method('add_msg', **msg) + res = self.call_method('report_{}_base'.format(cmd)) + self.assertIsNone(res['success']) + res = self.exec_cmd('state') + self.assertEqual(res['report_{}'.format(cmd)]['calls'], 1) + self.assertEqual(int(res['report_{}'.format(cmd)]['last_args']['c'], + base=16), + self.congure_state_ptr) + self.assertEqual(res['report_{}'.format(cmd)]['last_args']['msgs'], + msgs) + + def _report_msgs_timeout_lost_success(self, cmd): + msgs = [{'send_time': 76543, 'size': 1234, 'resends': 2}, + {'send_time': 5432, 'size': 987, 'resends': 32}] + res = self.call_method('report_{}'.format(cmd), msgs=msgs) + self.assertIsNone(res['success']) + res = self.exec_cmd('state') + self.assertEqual(res['report_{}'.format(cmd)]['calls'], 1) + self.assertEqual(int(res['report_{}'.format(cmd)]['last_args']['c'], + base=16), + self.congure_state_ptr) + self.assertEqual(res['report_{}'.format(cmd)]['last_args']['msgs'], + msgs) + + def test_report_msgs_timeout_base_success(self): + self._report_msgs_timeout_lost_base_success('msgs_timeout') + + def test_report_msgs_lost_base_success(self): + self._report_msgs_timeout_lost_base_success('msgs_lost') + + def test_report_msgs_timeout_success(self): + self._report_msgs_timeout_lost_success('msgs_timeout') + + def test_report_msgs_lost_success(self): + self._report_msgs_timeout_lost_success('msgs_lost') + + def test_report_msg_acked_base_success(self): + msg = {'send_time': 2862350241, 'size': 14679, 'resends': 0} + ack = {'recv_time': 2862350405, 'id': 1197554483, 'size': 14667, + 'clean': 1, 'wnd': 17440, 'delay': 33325} + res = self.call_method('add_msg', **msg) + self.assertIn('success', res) + res = self.call_method( + 'report_msg_acked_base', + **{'ack_{}'.format(k): v for k, v in ack.items()}) + self.assertIsNone(res['success']) + res = self.exec_cmd('state') + self.assertEqual(res['report_msg_acked']['calls'], 1) + self.assertEqual(int(res['report_msg_acked']['last_args']['c'], + base=16), + self.congure_state_ptr) + self.assertEqual(res['report_msg_acked']['last_args']['msg'], msg) + self.assertEqual(res['report_msg_acked']['last_args']['ack'], ack) + + def test_report_msg_acked_success(self): + msg = {'send_time': 2862350241, 'size': 14679, 'resends': 0} + ack = {'recv_time': 2862350405, 'id': 1197554483, 'size': 14667, + 'clean': 1, 'wnd': 17440, 'delay': 33325} + res = self.call_method('report_msg_acked', msg=msg, ack=ack) + self.assertIsNone(res['success']) + res = self.exec_cmd('state') + self.assertEqual(res['report_msg_acked']['calls'], 1) + self.assertEqual(int(res['report_msg_acked']['last_args']['c'], + base=16), + self.congure_state_ptr) + self.assertEqual(res['report_msg_acked']['last_args']['msg'], msg) + self.assertEqual(res['report_msg_acked']['last_args']['ack'], ack) + + def test_report_ecn_ce_success(self): + time = 64352 + res = self.call_method('report_ecn_ce', time=time) + self.assertIsNone(res['success']) + res = self.exec_cmd('state') + self.assertEqual(res['report_ecn_ce']['calls'], 1) + self.assertEqual(int(res['report_ecn_ce']['last_args']['c'], + base=16), + self.congure_state_ptr) + self.assertEqual(res['report_ecn_ce']['last_args']['time'], + time) + + if __name__ == '__main__': unittest.main()