From 90b0fb3bec902a500d092911db87bacf62ad8b8d Mon Sep 17 00:00:00 2001 From: Martine Lenders Date: Wed, 3 Feb 2021 10:45:19 +0100 Subject: [PATCH] tests: initial import of tests for CongURE and its test framework --- tests/congure_test/Makefile | 21 ++ tests/congure_test/README.md | 31 +++ tests/congure_test/app.config | 4 + tests/congure_test/congure_impl.c | 123 ++++++++ tests/congure_test/congure_impl.h | 77 +++++ tests/congure_test/main.c | 333 ++++++++++++++++++++++ tests/congure_test/tests/01-run.py | 432 +++++++++++++++++++++++++++++ 7 files changed, 1021 insertions(+) create mode 100644 tests/congure_test/Makefile create mode 100644 tests/congure_test/README.md create mode 100644 tests/congure_test/app.config create mode 100644 tests/congure_test/congure_impl.c create mode 100644 tests/congure_test/congure_impl.h create mode 100644 tests/congure_test/main.c create mode 100755 tests/congure_test/tests/01-run.py diff --git a/tests/congure_test/Makefile b/tests/congure_test/Makefile new file mode 100644 index 0000000000000..03d5ebafa3278 --- /dev/null +++ b/tests/congure_test/Makefile @@ -0,0 +1,21 @@ +include ../Makefile.tests_common + +USEMODULE += congure_test +USEMODULE += fmt +USEMODULE += shell +USEMODULE += shell_commands + +INCLUDES += -I$(CURDIR) + +include $(RIOTBASE)/Makefile.include + +ifndef CONFIG_SHELL_NO_ECHO + CFLAGS += -DCONFIG_SHELL_NO_ECHO=1 +endif + +ifndef CONFIG_CONGURE_TEST_LOST_MSG_POOL_SIZE + CFLAGS += -DCONFIG_CONGURE_TEST_LOST_MSG_POOL_SIZE=4 + export LOST_MSG_POOL_SIZE=4 +else + export LOST_MSG_POOL_SIZE=$(CONFIG_CONGURE_TEST_LOST_MSG_POOL_SIZE) +endif diff --git a/tests/congure_test/README.md b/tests/congure_test/README.md new file mode 100644 index 0000000000000..b652af6ad91d1 --- /dev/null +++ b/tests/congure_test/README.md @@ -0,0 +1,31 @@ +Basic tests for the CongURE API +=============================== + +This test tests the `congure_test` test framework used for the other CongURE +tests. + +Usage +----- + +The test requires an up-to-date version of `riotctrl` with `rapidjson` support: + +```console +$ pip install --upgrade riotctrl[rapidjson] +``` + +Then simply run the application using: + +```console +$ BOARD="" make flash test +``` + +It can also executed with pytest: + +```console +$ pytest tests/01-run.py +``` + +Expected result +--------------- + +The application's test script passes without error code. diff --git a/tests/congure_test/app.config b/tests/congure_test/app.config new file mode 100644 index 0000000000000..5c671d5e63f67 --- /dev/null +++ b/tests/congure_test/app.config @@ -0,0 +1,4 @@ +CONFIG_KCONFIG_USEMODULE_CONGURE_TEST=y +CONFIG_KCONFIG_USEMODULE_SHELL=y +CONFIG_CONGURE_TEST_LOST_MSG_POOL_SIZE=4 +CONFIG_SHELL_NO_ECHO=y diff --git a/tests/congure_test/congure_impl.c b/tests/congure_test/congure_impl.c new file mode 100644 index 0000000000000..7622f20db6649 --- /dev/null +++ b/tests/congure_test/congure_impl.c @@ -0,0 +1,123 @@ +/* + * 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. + */ + +/** + * @{ + * + * @file + * @author Martine Lenders + */ + +#include "congure_impl.h" + +static void _snd_init(congure_snd_t *cong, void *ctx); +static int32_t _snd_inter_msg_interval(congure_snd_t *cong, unsigned msg_size); +static void _snd_report_msg_sent(congure_snd_t *cong, unsigned msg_size); +static void _snd_report_msg_discarded(congure_snd_t *cong, unsigned msg_size); +static void _snd_report_msg_lost(congure_snd_t *cong, congure_snd_msg_t *msgs); +static void _snd_report_msg_timeout(congure_snd_t *cong, congure_snd_msg_t *msgs); +static void _snd_report_msg_acked(congure_snd_t *cong, congure_snd_msg_t *msg, + congure_snd_ack_t *ack); +static void _snd_report_ecn_ce(congure_snd_t *cong, ztimer_now_t time); + +static const congure_snd_driver_t _driver = { + .init = _snd_init, + .inter_msg_interval = _snd_inter_msg_interval, + .report_msg_sent = _snd_report_msg_sent, + .report_msg_discarded = _snd_report_msg_discarded, + .report_msg_timeout = _snd_report_msg_timeout, + .report_msg_lost = _snd_report_msg_lost, + .report_msg_acked = _snd_report_msg_acked, + .report_ecn_ce = _snd_report_ecn_ce, +}; + +int congure_test_snd_setup(congure_test_snd_t *c, unsigned id) +{ + if (id > 0) { + return -1; + } + c->super.driver = &_driver; + return 0; +} + +static void _snd_init(congure_snd_t *cong, void *ctx) +{ + congure_test_snd_t *c = (congure_test_snd_t *)cong; + + c->init_calls++; + c->init_args.c = &c->super; + c->init_args.ctx = ctx; +} + +static int32_t _snd_inter_msg_interval(congure_snd_t *cong, unsigned msg_size) +{ + congure_test_snd_t *c = (congure_test_snd_t *)cong; + + c->inter_msg_interval_calls++; + c->inter_msg_interval_args.c = &c->super; + c->inter_msg_interval_args.msg_size = msg_size; + return -1; +} + +static void _snd_report_msg_sent(congure_snd_t *cong, unsigned msg_size) +{ + congure_test_snd_t *c = (congure_test_snd_t *)cong; + + c->report_msg_sent_calls++; + c->report_msg_sent_args.c = &c->super; + c->report_msg_sent_args.msg_size = msg_size; +} + +static void _snd_report_msg_discarded(congure_snd_t *cong, unsigned msg_size) +{ + congure_test_snd_t *c = (congure_test_snd_t *)cong; + + c->report_msg_discarded_calls++; + c->report_msg_discarded_args.c = &c->super; + c->report_msg_discarded_args.msg_size = msg_size; +} + +static void _snd_report_msg_lost(congure_snd_t *cong, congure_snd_msg_t *msgs) +{ + congure_test_snd_t *c = (congure_test_snd_t *)cong; + + c->report_msg_lost_calls++; + c->report_msg_lost_args.c = &c->super; + c->report_msg_lost_args.msgs = msgs; +} + +static void _snd_report_msg_timeout(congure_snd_t *cong, congure_snd_msg_t *msgs) +{ + congure_test_snd_t *c = (congure_test_snd_t *)cong; + + c->report_msg_timeout_calls++; + c->report_msg_timeout_args.c = &c->super; + c->report_msg_timeout_args.msgs = msgs; +} + +static void _snd_report_msg_acked(congure_snd_t *cong, congure_snd_msg_t *msg, + congure_snd_ack_t *ack) +{ + congure_test_snd_t *c = (congure_test_snd_t *)cong; + + c->report_msg_acked_calls++; + c->report_msg_acked_args.c = &c->super; + c->report_msg_acked_args.msg = msg; + c->report_msg_acked_args.ack = ack; +} + +static void _snd_report_ecn_ce(congure_snd_t *cong, ztimer_now_t time) +{ + congure_test_snd_t *c = (congure_test_snd_t *)cong; + + c->report_ecn_ce_calls++; + c->report_ecn_ce_args.c = &c->super; + c->report_ecn_ce_args.time = time; +} + +/** @} */ diff --git a/tests/congure_test/congure_impl.h b/tests/congure_test/congure_impl.h new file mode 100644 index 0000000000000..1bd2fc10bc538 --- /dev/null +++ b/tests/congure_test/congure_impl.h @@ -0,0 +1,77 @@ +/* + * 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. + */ + +/** + * @{ + * + * @file + * + * @author Martine Lenders + */ +#ifndef CONGURE_IMPL_H +#define CONGURE_IMPL_H + +#include "congure.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + congure_snd_t super; + uint8_t init_calls; + uint8_t inter_msg_interval_calls; + uint8_t report_msg_sent_calls; + uint8_t report_msg_discarded_calls; + uint8_t report_msg_timeout_calls; + uint8_t report_msg_lost_calls; + uint8_t report_msg_acked_calls; + uint8_t report_ecn_ce_calls; + struct { + congure_snd_t *c; + void *ctx; + } init_args; + struct { + congure_snd_t *c; + unsigned msg_size; + } inter_msg_interval_args; + struct { + congure_snd_t *c; + unsigned msg_size; + } report_msg_sent_args; + struct { + congure_snd_t *c; + unsigned msg_size; + } report_msg_discarded_args; + struct { + congure_snd_t *c; + congure_snd_msg_t *msgs; + } report_msg_timeout_args; + struct { + congure_snd_t *c; + congure_snd_msg_t *msgs; + } report_msg_lost_args; + struct { + congure_snd_t *c; + congure_snd_msg_t *msg; + congure_snd_ack_t *ack; + } report_msg_acked_args; + struct { + congure_snd_t *c; + ztimer_now_t time; + } report_ecn_ce_args; +} congure_test_snd_t; + +int congure_test_snd_setup(congure_test_snd_t *c, unsigned id); + +#ifdef __cplusplus +} +#endif + +#endif /* CONGURE_IMPL_H */ +/** @} */ diff --git a/tests/congure_test/main.c b/tests/congure_test/main.c new file mode 100644 index 0000000000000..fdc52adba117d --- /dev/null +++ b/tests/congure_test/main.c @@ -0,0 +1,333 @@ +/* + * 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. + */ + +/** + * @{ + * + * @file + * @author Martine S. Lenders + */ + +#include + +#include "clist.h" +#include "congure/test.h" +#include "fmt.h" +#include "shell.h" + +#include "congure_impl.h" + +static int _json_statham(int argc, char **argv); + +static congure_test_snd_t _congure_state; +static const shell_command_t shell_commands[] = { + { "state", "Prints current CongURE state object as JSON", _json_statham }, + { NULL, NULL, NULL } +}; + +int main(void) +{ + char line_buf[SHELL_DEFAULT_BUFSIZE]; + shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE); + return 0; +} + +congure_test_snd_t *congure_test_get_state(void) +{ + return &_congure_state; +} + +static int _print_congure_snd_msg(clist_node_t *node, void *arg) +{ + congure_snd_msg_t *msg = (congure_snd_msg_t *)node; + + (void)arg; + print_str("{"); + + print_str("\"send_time\":"); + print_u64_dec(msg->send_time); + print_str(","); + + print_str("\"size\":"); + print_u32_dec(msg->size); + print_str(","); + + print_str("\"resends\":"); + print_u32_dec(msg->resends); + + print_str("},"); + + return 0; +} + +static int _print_congure_snd_ack(congure_snd_ack_t *ack) +{ + print_str("{"); + + print_str("\"recv_time\":"); + print_u64_dec(ack->recv_time); + print_str(","); + + print_str("\"id\":"); + print_u32_dec(ack->id); + print_str(","); + + print_str("\"size\":"); + print_u32_dec(ack->size); + print_str(","); + + print_str("\"clean\":"); + print_str(ack->clean ? "true" : "false"); + print_str(","); + + print_str("\"wnd\":"); + print_u32_dec(ack->wnd); + print_str(","); + + print_str("\"delay\":"); + print_u32_dec(ack->delay); + print_str(","); + + print_str("},"); + + return 0; +} + +static void _print_init_state(void) +{ + print_str("\"init\":{"); + + print_str("\"calls\":"); + print_u32_dec(_congure_state.init_calls); + print_str(","); + + print_str("\"last_args\":{"); + + print_str("\"c\":\"0x"); + print_u32_hex((intptr_t)_congure_state.init_args.c); + print_str("\","); + + print_str("\"ctx\":\"0x"); + print_u32_hex((intptr_t)_congure_state.init_args.ctx); + print_str("\""); + + print_str("}"); + + print_str("},"); +} + +static void _print_inter_msg_interval_state(void) +{ + print_str("\"inter_msg_interval\":{"); + + print_str("\"calls\":"); + print_u32_dec(_congure_state.inter_msg_interval_calls); + print_str(","); + + print_str("\"last_args\":{"); + + print_str("\"c\":\"0x"); + print_u32_hex((intptr_t)_congure_state.inter_msg_interval_args.c); + print_str("\","); + + print_str("\"msg_size\":"); + print_u32_dec(_congure_state.inter_msg_interval_args.msg_size); + print_str(""); + + print_str("}"); + + print_str("},"); +} + +static void _print_report_msg_sent_state(void) +{ + print_str("\"report_msg_sent\":{"); + + print_str("\"calls\":"); + print_u32_dec(_congure_state.report_msg_sent_calls); + print_str(","); + + print_str("\"last_args\":{"); + + print_str("\"c\":\"0x"); + print_u32_hex((intptr_t)_congure_state.report_msg_sent_args.c); + print_str("\","); + + print_str("\"msg_size\":"); + print_u32_dec(_congure_state.report_msg_sent_args.msg_size); + print_str(""); + + print_str("}"); + + print_str("},"); +} + +static void _print_report_msg_discarded_state(void) +{ + print_str("\"report_msg_discarded\":{"); + + print_str("\"calls\":"); + print_u32_dec(_congure_state.report_msg_discarded_calls); + print_str(","); + + print_str("\"last_args\":{"); + + print_str("\"c\":\"0x"); + print_u32_hex((intptr_t)_congure_state.report_msg_discarded_args.c); + print_str("\","); + + print_str("\"msg_size\":"); + print_u32_dec(_congure_state.report_msg_discarded_args.msg_size); + print_str(""); + + print_str("}"); + + print_str("},"); +} + +static void _print_report_msg_timeout_state(void) +{ + print_str("\"report_msg_timeout\":{"); + + print_str("\"calls\":"); + print_u32_dec(_congure_state.report_msg_timeout_calls); + print_str(","); + + print_str("\"last_args\":{"); + + print_str("\"c\":\"0x"); + print_u32_hex((intptr_t)_congure_state.report_msg_timeout_args.c); + print_str("\","); + + print_str("\"msgs\":["); + clist_foreach((clist_node_t *)&_congure_state.report_msg_timeout_args.msgs, + _print_congure_snd_msg, NULL); + print_str("]"); + + print_str("}"); + + print_str("},"); +} + +static void _print_report_msg_lost_state(void) +{ + print_str("\"report_msg_lost\":{"); + + print_str("\"calls\":"); + print_u32_dec(_congure_state.report_msg_lost_calls); + print_str(","); + + print_str("\"last_args\":{"); + + print_str("\"c\":\"0x"); + print_u32_hex((intptr_t)_congure_state.report_msg_lost_args.c); + print_str("\","); + + print_str("\"msgs\":["); + clist_foreach((clist_node_t *)&_congure_state.report_msg_lost_args.msgs, + _print_congure_snd_msg, NULL); + print_str("]"); + + print_str("}"); + + print_str("},"); +} + +static void _print_report_msg_acked_state(void) +{ + print_str("\"report_msg_acked\":{"); + + print_str("\"calls\":"); + print_u32_dec(_congure_state.report_msg_acked_calls); + print_str(","); + + print_str("\"last_args\":{"); + + print_str("\"c\":\"0x"); + print_u32_hex((intptr_t)_congure_state.report_msg_acked_args.c); + print_str("\","); + + assert((_congure_state.report_msg_acked_args.msg == NULL) || + (_congure_state.report_msg_acked_args.msg->super.next == NULL)); + print_str("\"msg\":"); + if (_congure_state.report_msg_acked_args.msg) { + _print_congure_snd_msg( + (clist_node_t *)_congure_state.report_msg_acked_args.msg, NULL + ); + } + else { + print_str("null,"); + } + + print_str("\"ack\":"); + if (_congure_state.report_msg_acked_args.ack) { + _print_congure_snd_ack(_congure_state.report_msg_acked_args.ack); + } + else { + print_str("null"); + } + + print_str("}"); + + print_str("},"); +} + +static void _print_report_ecn_ce_state(void) +{ + print_str("\"report_ecn_ce\":{"); + + print_str("\"calls\":"); + print_u32_dec(_congure_state.report_ecn_ce_calls); + print_str(","); + + print_str("\"last_args\":{"); + + print_str("\"c\":\"0x"); + print_u32_hex((intptr_t)_congure_state.report_ecn_ce_args.c); + print_str("\","); + + print_str("\"time\":"); + print_u64_dec((intptr_t)_congure_state.report_ecn_ce_args.time); + print_str(","); + + print_str("}"); + + print_str("},"); +} + +static int _json_statham(int argc, char **argv) +{ + (void)argc; + (void)argv; + print_str("{"); + print_str("\"driver\":\"0x"); + print_u32_hex((intptr_t)_congure_state.super.driver); + print_str("\","); + + print_str("\"ctx\":\"0x"); + print_u32_hex((intptr_t)_congure_state.super.ctx); + print_str("\","); + + print_str("\"cwnd\":"); + print_u32_dec(_congure_state.super.cwnd); + print_str(","); + + _print_init_state(); + _print_inter_msg_interval_state(); + _print_report_msg_sent_state(); + _print_report_msg_discarded_state(); + _print_report_msg_timeout_state(); + _print_report_msg_lost_state(); + _print_report_msg_acked_state(); + _print_report_ecn_ce_state(); + + print_str("}\n"); + return 0; +} + +/** @} */ diff --git a/tests/congure_test/tests/01-run.py b/tests/congure_test/tests/01-run.py new file mode 100755 index 0000000000000..0d7d9cf762636 --- /dev/null +++ b/tests/congure_test/tests/01-run.py @@ -0,0 +1,432 @@ +#! /usr/bin/env python3 + +# 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 logging +import os +import unittest + +from riotctrl.ctrl import RIOTCtrl +from riotctrl.shell import ShellInteraction +from riotctrl.shell.json import RapidJSONShellInteractionParser, rapidjson + + +class TestCongUREBase(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.ctrl = RIOTCtrl() + cls.ctrl.start_term() + cls.ctrl.reset() + cls.shell = ShellInteraction(cls.ctrl) + cls.json_parser = RapidJSONShellInteractionParser() + cls.json_parser.set_parser_args( + parse_mode=rapidjson.PM_TRAILING_COMMAS + ) + cls.logger = logging.getLogger(cls.__name__) + + @classmethod + def tearDownClass(cls): + cls.ctrl.stop_term() + + def setUp(self): + self.shell.cmd('cong_clear') + + def exec_cmd(self, cmd, timeout=-1, async_=False): + res = self.shell.cmd(cmd, timeout, async_) + self.logger.debug(res) + if res.strip(): + return self.json_parser.parse(res) + return None + + +class TestCongUREWithoutSetup(TestCongUREBase): + def test_no_setup(self): + state = self.exec_cmd('state') + self.assertEqual(state, { + 'driver': '0x00000000', + 'ctx': '0x00000000', + 'cwnd': 0, + 'init': { + 'calls': 0, + 'last_args': { + 'c': '0x00000000', + 'ctx': '0x00000000', + }, + }, + 'inter_msg_interval': { + 'calls': 0, + 'last_args': { + 'c': '0x00000000', + 'msg_size': 0, + }, + }, + 'report_msg_sent': { + 'calls': 0, + 'last_args': { + 'c': '0x00000000', + 'msg_size': 0, + }, + }, + 'report_msg_discarded': { + 'calls': 0, + 'last_args': { + 'c': '0x00000000', + 'msg_size': 0, + }, + }, + 'report_msg_timeout': { + 'calls': 0, + 'last_args': { + 'c': '0x00000000', + 'msgs': [], + }, + }, + 'report_msg_lost': { + 'calls': 0, + 'last_args': { + 'c': '0x00000000', + 'msgs': [], + }, + }, + 'report_msg_acked': { + 'calls': 0, + 'last_args': { + 'c': '0x00000000', + 'msg': None, + 'ack': None, + }, + }, + 'report_ecn_ce': { + 'calls': 0, + 'last_args': { + 'c': '0x00000000', + 'time': 0, + }, + }, + }) + + def test_setup_invalid_id(self): + self.test_no_setup() + res = self.exec_cmd('cong_setup abcd') + self.assertEqual(res, {'error': "`id` expected to be integer"}) + res = self.exec_cmd('cong_setup 1') + self.assertEqual(res, {'error': "`id` is invalid"}) + + def test_setup(self): + self.test_no_setup() + res = self.exec_cmd('cong_setup') + # res['setup'] is 32-bit hexadecimal number greater zero, representing + # the pointer to the congure state object + self.assertGreater(int(res['setup'], base=16), 0) + self.assertEqual(len(res['setup']), len('0x00000000')) + res = self.exec_cmd('cong_setup 0') + # res['setup'] is 32-bit hexadecimal number greater zero, representing + # the pointer to the congure state object + self.assertGreater(int(res['setup'], base=16), 0) + self.assertEqual(len(res['setup']), len('0x00000000')) + + def test_init_wo_setup(self): + res = self.exec_cmd('cong_init 0x12345') + self.assertEqual(res, {'error': "State object not set up"}) + + def test_inter_msg_interval_wo_setup(self): + res = self.exec_cmd('cong_imi 689') + self.assertEqual(res, {'error': "State object not set up"}) + + def test_report_wo_setup(self): + res = self.exec_cmd('cong_report') + self.assertEqual(res, {'error': "State object not set up"}) + + +class TestCongUREWithSetup(TestCongUREBase): + def setUp(self): + super().setUp() + res = self.exec_cmd('cong_setup') + self.congure_state_ptr = int(res['setup'], base=16) + + def test_init_no_args(self): + res = self.exec_cmd('cong_init') + self.assertEqual(res, {'error': "`ctx` argument expected"}) + + def test_init_arg_not_hex(self): + res = self.exec_cmd('cong_init foobar') + self.assertEqual(res, {'error': "`ctx` expected to be hex"}) + res = self.exec_cmd('cong_init 123456') + self.assertEqual(res, {'error': "`ctx` expected to be hex"}) + + def test_init_success(self): + ctx = 0x12345 + res = self.exec_cmd(f'cong_init 0x{ctx:x}') + self.assertIsNone(res) + 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_no_args(self): + res = self.exec_cmd('cong_imi') + self.assertEqual(res, {'error': '`msg_size` argument expected'}) + + def test_inter_msg_interval_not_int(self): + res = self.exec_cmd('cong_imi foobar') + self.assertEqual(res, {'error': '`msg_size` expected to be integer'}) + + def test_inter_msg_interval_success(self): + msg_size = 521 + res = self.exec_cmd(f'cong_imi {msg_size}') + assert res == {'inter_msg_interval': -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_no_args(self): + res = self.exec_cmd('cong_report foobar') + self.assertEqual(res, {'error': 'Unknown command `foobar`'}) + + def test_report_unknown_command(self): + res = self.exec_cmd('cong_report') + self.assertEqual(res, {'error': 'No report command provided'}) + + def test_report_msg_sent_no_args(self): + res = self.exec_cmd('cong_report msg_sent') + self.assertEqual(res, {'error': '`msg_size` argument expected'}) + + def test_report_msg_sent_not_int(self): + res = self.exec_cmd('cong_report msg_sent this one') + self.assertEqual(res, {'error': '`msg_size` expected to be integer'}) + + def test_report_msg_sent_success(self): + msg_size = 1234 + res = self.exec_cmd(f'cong_report msg_sent {msg_size}') + self.assertIsNone(res) + 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_no_args(self): + res = self.exec_cmd('cong_report msg_discarded') + self.assertEqual(res, {'error': "`msg_size` argument expected"}) + + def test_report_msg_discarded_not_int(self): + res = self.exec_cmd('cong_report msg_discarded this one') + self.assertEqual(res, {'error': "`msg_size` expected to be integer"}) + + def test_report_msg_discarded_success(self): + msg_size = 1234 + res = self.exec_cmd(f'cong_report msg_discarded {msg_size}') + self.assertIsNone(res) + 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_msg_timeout_lost_acked_not_enough_args(self, cmd, exp_params): + args = "" + # gradually append more arguments but never get full set + for i in range(len(exp_params) - 1): + args += f' {i+1}' + res = self.exec_cmd(f'cong_report {cmd} {args}') + self.assertEqual(res, { + 'error': 'At least {} arguments {} expected' + .format(len(exp_params), + ', '.join(f'`{p}`' for p in exp_params)) + }) + + def _report_msg_timeout_lost_argc_not_mod_3(self, cmd): + res = self.exec_cmd(f'cong_report {cmd} 1 2 3 4') + self.assertEqual(res, { + 'error': 'Number of arguments must be divisible by 3' + }) + res = self.exec_cmd(f'cong_report {cmd} 1 2 3 4 5') + self.assertEqual(res, { + 'error': 'Number of arguments must be divisible by 3' + }) + + def _report_msg_timeout_lost_acked_args_not_int(self, cmd, exp_params): + # generate list of arguments that are exp_params string parameters and + # exp_params integer parameters + args = [f"arg{i}" for i in range(len(exp_params))] + \ + [str(i + len(exp_params)) for i in range(len(exp_params))] + res = self.exec_cmd('cong_report {} {}'.format(cmd, ' '.join(args))) + self.assertEqual(res, { + 'error': f'`{exp_params[0]}` expected to be integer' + }) + # gradually transform all but the last string to integer and test again + for i in range(len(exp_params) - 1): + args[i] = str(i + 1) + res = self.exec_cmd( + 'cong_report {} {}'.format(cmd, ' '.join(args)) + ) + self.assertEqual(res, { + 'error': f'`{exp_params[i + 1]}` expected to be integer' + }) + + def _report_msg_timeout_lost_exceed_msg_pool_size(self, cmd): + # expected to be set by Makefile + pool_size = int(os.environ.get('LOST_MSG_POOL_SIZE', 4)) + args = ' '.join('1' for _ in range(3 * pool_size)) + args += ' 1 1 1' + res = self.exec_cmd(f'cong_report {cmd} {args}') + self.assertEqual(res, { + 'error': 'List element pool depleted' + }) + + def _report_msg_timeout_lost_success(self, cmd): + msgs = [{'send_time': 76543, 'size': 1234, 'resends': 2}, + {'send_time': 5432, 'size': 987, 'resends': 32}] + res = self.exec_cmd( + f'cong_report {cmd} ' + f'{msgs[0]["send_time"]} {msgs[0]["size"]} {msgs[0]["resends"]} ' + f'{msgs[1]["send_time"]} {msgs[1]["size"]} {msgs[1]["resends"]}' + ) + self.assertIsNone(res) + res = self.exec_cmd('state') + self.assertEqual(res[f'report_{cmd}']['calls'], 1) + self.assertEqual(int(res[f'report_{cmd}']['last_args']['c'], + base=16), + self.congure_state_ptr) + self.assertEqual(res[f'report_{cmd}']['last_args']['msgs'], + msgs) + + def test_report_msg_timeout_not_enough_args(self): + self._report_msg_timeout_lost_acked_not_enough_args( + 'msg_timeout', ['msg_send_time', 'msg_size', 'msg_resends'] + ) + + def test_report_msg_timeout_argc_not_mod_3(self): + self._report_msg_timeout_lost_argc_not_mod_3('msg_timeout') + + def test_report_msg_timeout_args_not_int(self): + self._report_msg_timeout_lost_acked_args_not_int( + 'msg_timeout', ['msg_send_time', 'msg_size', 'msg_resends'] + ) + + def test_report_msg_timeout_exceed_msg_pool_size(self): + self._report_msg_timeout_lost_exceed_msg_pool_size('msg_timeout') + + def test_report_msg_timeout_success(self): + self._report_msg_timeout_lost_success('msg_timeout') + + def test_report_msg_lost_not_enough_args(self): + self._report_msg_timeout_lost_acked_not_enough_args( + 'msg_lost', ['msg_send_time', 'msg_size', 'msg_resends'] + ) + + def test_report_msg_lost_argc_not_mod_3(self): + self._report_msg_timeout_lost_argc_not_mod_3('msg_lost') + + def test_report_msg_lost_msg_args_not_int(self): + self._report_msg_timeout_lost_acked_args_not_int( + 'msg_lost', ['msg_send_time', 'msg_size', 'msg_resends'] + ) + + def test_report_msg_lost_exceed_msg_pool_size(self): + self._report_msg_timeout_lost_exceed_msg_pool_size('msg_lost') + + def test_report_msg_lost_success(self): + self._report_msg_timeout_lost_success('msg_lost') + + def test_report_msg_acked_not_enough_args(self): + self._report_msg_timeout_lost_acked_not_enough_args( + 'msg_acked', [ + 'msg_send_time', 'msg_size', 'msg_resends', 'ack_recv_time', + 'ack_id', 'ack_size', 'ack_clean', 'ack_wnd', 'ack_delay', + ] + + ) + + def test_report_msg_acked_msg_args_not_int(self): + self._report_msg_timeout_lost_acked_args_not_int( + 'msg_acked', [ + 'msg_send_time', 'msg_size', 'msg_resends', 'ack_recv_time', + 'ack_id', 'ack_size', 'ack_clean', 'ack_wnd', 'ack_delay', + ] + ) + + def test_report_msg_acked_wnd_not_wnd_size(self): + msg = {'send_time': 12345, 'size': 456, 'resends': 0} + ack = {'recv_time': 12432, 'id': 1715718846, 'size': 12, + 'clean': 1, 'wnd': (1 << 16) + 7642, 'delay': 1235} + res = self.exec_cmd( + f'cong_report msg_acked ' + f'{msg["send_time"]} {msg["size"]} {msg["resends"]} ' + f'{ack["recv_time"]} {ack["id"]} {ack["size"]} {ack["clean"]} ' + f'{ack["wnd"]} {ack["delay"]}' + ) + self.assertEqual(res, { + 'error': '`ack_wnd` not 16 bit wide' + }) + + def test_report_msg_acked_delay_not_uint16(self): + msg = {'send_time': 12345, 'size': 456, 'resends': 0} + ack = {'recv_time': 12432, 'id': 1715718846, 'size': 12, + 'clean': 1, 'wnd': 7642, 'delay': (1 << 16) + 1235} + res = self.exec_cmd( + f'cong_report msg_acked ' + f'{msg["send_time"]} {msg["size"]} {msg["resends"]} ' + f'{ack["recv_time"]} {ack["id"]} {ack["size"]} {ack["clean"]} ' + f'{ack["wnd"]} {ack["delay"]}' + ) + self.assertEqual(res, { + 'error': '`ack_delay` not 16 bit wide' + }) + + def test_report_msg_acked_success(self): + msg = {'send_time': 12345, 'size': 456, 'resends': 0} + ack = {'recv_time': 12432, 'id': 1715718846, 'size': 12, + 'clean': 1, 'wnd': 742, 'delay': 1235} + res = self.exec_cmd( + f'cong_report msg_acked ' + f'{msg["send_time"]} {msg["size"]} {msg["resends"]} ' + f'{ack["recv_time"]} {ack["id"]} {ack["size"]} {ack["clean"]} ' + f'{ack["wnd"]} {ack["delay"]}' + ) + self.assertIsNone(res) + 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_no_args(self): + res = self.exec_cmd('cong_report ecn_ce') + self.assertEqual(res, {'error': '`time` argument expected'}) + + def test_report_ecn_ce_not_int(self): + res = self.exec_cmd('cong_report ecn_ce this one') + self.assertEqual(res, {'error': '`time` expected to be integer'}) + + def test_report_ecn_ce_success(self): + time = 64352 + res = self.exec_cmd(f'cong_report ecn_ce {time}') + self.assertIsNone(res) + 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()