/** * Copyright 2013-2023 Software Radio Systems Limited * * This file is part of srsRAN. * * srsRAN is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * srsRAN is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * A copy of the GNU Affero General Public License can be found in * the LICENSE file in the top-level directory of this distribution * and at http://www.gnu.org/licenses/. * */ #include "srsran/common/stack_procedure.h" #include #define TESTASSERT(cond) \ { \ if (!(cond)) { \ std::cout << "[" << __FUNCTION__ << "][Line " << __LINE__ << "]: FAIL at " << (#cond) << std::endl; \ return -1; \ } \ } using srsran::proc_outcome_t; enum class obj_state_t { default_ctor, move_ctor, copy_ctor, from_move_ctor, from_move_assign }; const char* to_string(obj_state_t o) { switch (o) { case obj_state_t::default_ctor: return "default_ctor"; case obj_state_t::move_ctor: return "move_ctor"; case obj_state_t::copy_ctor: return "copy_ctor"; case obj_state_t::from_move_ctor: return "from_move_ctor"; case obj_state_t::from_move_assign: return "from_move_assign"; } return ""; } class TestObj { public: TestObj() = default; TestObj(const TestObj& other) { printf("TestObj copy ctor called: {%s,%d} <- {%s,%d}!!!\n", to_string(state), id, to_string(other.state), other.id); id = other.id; state = obj_state_t::copy_ctor; counters = other.counters; copy_counter++; } TestObj(TestObj&& other) noexcept { printf("TestObj move ctor called: {%s,%d} <- {%s,%d}!!!\n", to_string(state), id, to_string(other.state), other.id); id = other.id; state = obj_state_t::move_ctor; counters = other.counters; other.state = obj_state_t::from_move_ctor; move_counter++; } ~TestObj() { dtor_counter++; printf("TestObj {%s,%d} dtor called!!!\n", to_string(state), id); } TestObj& operator=(const TestObj& other) { printf("TestObj copy operator called: {%s,%d} <- {%s,%d}!!!\n", to_string(state), id, to_string(other.state), other.id); id = other.id; state = other.state; counters = other.counters; copy_counter++; return *this; } TestObj& operator=(TestObj&& other) noexcept { printf("TestObj move operator called: {%s,%d} <- {%s,%d}!!!\n", to_string(state), id, to_string(other.state), other.id); if (&other != this) { id = other.id; state = other.state; counters = other.counters; other.state = obj_state_t::from_move_assign; move_counter++; } return *this; } obj_state_t state = obj_state_t::default_ctor; int id = 0; struct stats_t { int then_counter = 0; int reset_counter = 0; }; mutable stats_t counters; static int copy_counter; static int move_counter; static int dtor_counter; }; int TestObj::copy_counter = 0; int TestObj::move_counter = 0; int TestObj::dtor_counter = 0; void new_test() { TestObj::copy_counter = 0; TestObj::move_counter = 0; TestObj::dtor_counter = 0; } class custom_proc { public: custom_proc() : ctor_value(5) {} proc_outcome_t init(int a_) { if (a_ < 0) { printf("Failed to initiate custom_proc\n"); return proc_outcome_t::error; } obj.id = a_; return proc_outcome_t::yield; } proc_outcome_t step() { if (counter++ > 5) { return proc_outcome_t::success; } return proc_outcome_t::yield; } void then(const srsran::proc_result_t& result) const { printf("TestObj %d then() was called\n", obj.id); obj.counters.then_counter++; } const char* name() const { return "custom proc"; } void clear() { reset_called = true; printf("TestObj was reset\n"); } int get_result() const { return obj.id; } TestObj obj; const int ctor_value = 5; bool reset_called = false; private: int counter = 0; }; class custom_proc2_t { public: proc_outcome_t init() { exit_val = "init"; event_val = ""; counter = 0; return proc_outcome_t::yield; } proc_outcome_t step() { if (counter++ > 5) { exit_val = "success"; return proc_outcome_t::success; } return proc_outcome_t::yield; } // trigger itf struct event_t { std::string event_val; }; proc_outcome_t react(const event_t& event) { event_val = event.event_val; return proc_outcome_t::yield; } std::string get_result() const { return exit_val; } std::string exit_val = ""; std::string event_val = ""; int counter = 0; }; static_assert(std::is_same::result_type, int>::value, "Failed derivation of result type"); static_assert(std::is_same::result_type, std::string>::value, "Failed derivation of result type"); int test_local_1() { /* * Description: Test if a procedure is cleaned automatically after its lifetime has ended */ new_test(); printf("\n--- Test %s ---\n", __func__); srsran::proc_t proc; TESTASSERT(proc.is_idle() and not proc.is_busy()) proc.launch(1); TESTASSERT(not proc.is_idle() and proc.is_busy()) TESTASSERT(not proc.get()->reset_called) while (proc.run()) { } TESTASSERT(proc.is_idle() and not proc.is_busy()) TESTASSERT(proc.get()->obj.counters.then_counter == 1) TESTASSERT(proc.get()->reset_called) // Proc is ready to be reused const custom_proc& procobj = *proc.get(); TESTASSERT(procobj.obj.id == 1) TESTASSERT(not proc.is_busy() and proc.is_idle()) TESTASSERT(proc.get()->ctor_value == 5) printf("EXIT\n"); TESTASSERT(TestObj::copy_counter == 0); TESTASSERT(TestObj::move_counter == 0); TESTASSERT(TestObj::dtor_counter == 0); // destructor not called yet return 0; } int test_callback_1() { /* * Description: Test a procedure inserted in a manager list via "proc_manager_list_t::add_proc(...)" * - check if the proc is cleared automatically after it finished * - check if the proc_future value is correctly updated * - check if creating a new future does not affect previous one */ new_test(); printf("\n--- Test %s ---\n", __func__); srsran::proc_manager_list_t callbacks; srsran::proc_t proc; TESTASSERT(not proc.is_busy() and proc.is_idle()) TESTASSERT(proc.launch(2)) callbacks.add_proc(proc); // We have to call pop() explicitly to take the result TESTASSERT(callbacks.size() == 1) srsran::proc_future_t proc_fut = proc.get_future(); while (callbacks.size() > 0) { TESTASSERT(not proc_fut.is_complete()) TESTASSERT(proc.is_busy()) callbacks.run(); } TESTASSERT(proc.is_idle()); TESTASSERT(proc_fut.is_success() and *proc_fut.value() == 2) TESTASSERT(proc.get()->obj.id == 2) TESTASSERT(proc.get()->obj.counters.then_counter == 1) TESTASSERT(proc.get()->reset_called) // Proc is ready to be reused srsran::proc_future_t proc_fut2 = proc.get_future(); TESTASSERT(not proc_fut2.is_complete() and proc_fut.is_complete()) printf("EXIT\n"); TESTASSERT(TestObj::copy_counter == 0); TESTASSERT(TestObj::move_counter == 0); TESTASSERT(TestObj::dtor_counter == 0); // handler not yet destructed return 0; } int test_callback_2() { /* * Description: Test a procedure inserted in a manager list as an r-value * - check if the proc disappears automatically after it finished */ new_test(); printf("\n--- Test %s ---\n", __func__); srsran::proc_manager_list_t callbacks; srsran::proc_t proc; TESTASSERT(not proc.is_busy()); srsran::proc_future_t fut = proc.get_future(); TESTASSERT(fut.is_empty()); TESTASSERT(proc.launch(&fut, 3)); TESTASSERT(proc.is_busy()); callbacks.add_proc(std::move(proc)); TESTASSERT(callbacks.size() == 1); while (callbacks.size() > 0) { callbacks.run(); } TESTASSERT(fut.is_success() and *fut.value() == 3) printf("EXIT\n"); TESTASSERT(TestObj::copy_counter == 0); TESTASSERT(TestObj::move_counter == 0); // it does not move proc itself, but its pointer TESTASSERT(TestObj::dtor_counter == 1); // handler not yet destructed, but we called proc move return 0; } int test_callback_3() { /* * Description: Test for Lambda procedure types * - test if a lambda that we passed decrements a counter correctly * - test if when the lambda goes out of scope, procedure still works fine */ new_test(); printf("\n--- Test %s ---\n", __func__); srsran::proc_manager_list_t callbacks; int* counter = new int(5); { callbacks.add_task([counter]() { printf("current counter=%d\n", *counter); if (--(*counter) == 0) { return proc_outcome_t::success; } return proc_outcome_t::yield; }); } while (callbacks.size() > 0) { callbacks.run(); } TESTASSERT(*counter == 0); delete counter; return 0; } int test_callback_4() { /* * Description: Test if finished procedure does not get added to the dispatch list */ new_test(); printf("\n--- Test %s ---\n", __func__); srsran::proc_manager_list_t callbacks; srsran::proc_t proc; TESTASSERT(proc.launch(5)); while (proc.run()) { TESTASSERT(proc.is_busy()); } TESTASSERT(proc.is_idle()); TESTASSERT(proc.get()->obj.counters.then_counter == 1) TESTASSERT(proc.get()->reset_called); callbacks.add_proc(proc); TESTASSERT(callbacks.size() == 0); // do not add finished callbacks return 0; } int test_complete_callback_1() { /* * Description: Test if then() callbacks are correctly called */ printf("\n--- Test %s ---\n", __func__); srsran::proc_manager_list_t callbacks; srsran::proc_t proc; std::string run_result; auto continuation_task = [&run_result](const srsran::proc_result_t& e) { run_result = e.is_success() ? "SUCCESS" : "FAILURE"; }; const std::string results[] = {"", "SUCCESS", "", "SUCCESS", "SUCCESS", "SUCCESS"}; for (uint32_t i = 0; i < 6; ++i) { run_result = ""; if (i == 1) { TESTASSERT(proc.then(continuation_task) == 0) } else if (i == 3) { TESTASSERT(proc.then_always(continuation_task) == 0) } srsran::proc_future_t fut; TESTASSERT(proc.launch(&fut)); TESTASSERT(proc.get()->exit_val == "init") while (proc.run()) { TESTASSERT(proc.get()->exit_val == "init") TESTASSERT(proc.is_busy()) } TESTASSERT(proc.is_idle() and proc.get()->exit_val == "success") TESTASSERT(fut.is_success() and *fut.value() == "success") TESTASSERT(run_result == results[i]) } return 0; } int test_event_handler_1() { /* * Description: Test if event handler calls trigger for multiple procedures */ printf("\n--- Test %s ---\n", __func__); srsran::proc_t proc, proc2; srsran::event_handler_t ev_handler; TESTASSERT(proc.launch()) TESTASSERT(proc2.launch()) TESTASSERT(proc.is_busy() and proc2.is_busy()) TESTASSERT(proc.get()->exit_val == "init" and proc2.get()->exit_val == "init") TESTASSERT(proc.get()->event_val.empty() and proc2.get()->event_val.empty()) ev_handler.on_next_trigger(proc); ev_handler.on_every_trigger(proc2); ev_handler.trigger(custom_proc2_t::event_t{"event1"}); TESTASSERT(proc.get()->event_val == "event1" and proc.get()->event_val == "event1") ev_handler.trigger(custom_proc2_t::event_t{"event2"}); TESTASSERT(proc.get()->event_val == "event1" and proc2.get()->event_val == "event2") printf("Procedures correctly triggered by event handler\n"); return 0; } int main() { TESTASSERT(test_local_1() == 0) TESTASSERT(test_callback_1() == 0) TESTASSERT(test_callback_2() == 0) TESTASSERT(test_callback_3() == 0) TESTASSERT(test_callback_4() == 0) TESTASSERT(test_complete_callback_1() == 0) TESTASSERT(test_event_handler_1() == 0) std::cout << "\n---------------\nResult: Success\n"; return 0; }