From f479e66dd16d2758be98b613768559cc4abab70f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Zieli=C5=84ski?= Date: Wed, 26 Oct 2022 16:07:03 +0200 Subject: [PATCH] [MOS-770] Replace VoLTE state switching mechanism Added unit tests. --- .../service-cellular/src/VolteHandler.hpp | 9 +- .../service-cellular/src/VolteHandlerImpl.cpp | 6 +- .../service-cellular/tests/CMakeLists.txt | 9 + .../tests/unittest_volteHandler.cpp | 230 ++++++++++++++++++ 4 files changed, 246 insertions(+), 8 deletions(-) create mode 100644 module-services/service-cellular/tests/unittest_volteHandler.cpp diff --git a/module-services/service-cellular/src/VolteHandler.hpp b/module-services/service-cellular/src/VolteHandler.hpp index d094d826176f9ebd44835ad5ad5973b887e64f88..b0a3998c35fafd385bc87e429e5720ddcc133958 100644 --- a/module-services/service-cellular/src/VolteHandler.hpp +++ b/module-services/service-cellular/src/VolteHandler.hpp @@ -50,20 +50,23 @@ namespace cellular::service } // can be left as always on: doesn't disturb when VoLTE disabled - auto qmbnAnswer = - channel.cmd(factory(at::AT::QMBNCFG) + std::string("\"autosel\",1")); + auto qmbnAnswer = channel.cmd(factory(at::AT::QMBNCFG) + std::string("\"autosel\",1")); if (!qmbnAnswer) { throw std::runtime_error("[VoLTE] failed to enable MBN auto-select before trying to enable VoLTE"); } } auto imsCheckAnswer = channel.cmd(factory(AT::QCFG_IMS)); + if (!imsCheckAnswer) { + throw std::runtime_error("[VoLTE] modem responded with error to QCFG_IMS"); + } + bool alreadyConfigured; try { alreadyConfigured = parser(QcfgImsResult{imsCheckAnswer}, enable); } catch (std::runtime_error const &exc) { - throw std::runtime_error(std::string("[VoLTE] while checking IMS configuration state: ") + exc.what()); + throw; } if (!alreadyConfigured) { diff --git a/module-services/service-cellular/src/VolteHandlerImpl.cpp b/module-services/service-cellular/src/VolteHandlerImpl.cpp index a41a8fd0b9d5f2b26ee72bf2fa95fe32720ed798..c71ce772197ce30aa0abc45c77f1bd5845a51286 100644 --- a/module-services/service-cellular/src/VolteHandlerImpl.cpp +++ b/module-services/service-cellular/src/VolteHandlerImpl.cpp @@ -9,13 +9,9 @@ namespace cellular::internal { using namespace at::response::qcfg_ims; - if (!response) { - throw std::runtime_error("[VoLTE] modem responded with error to QCFG_IMS"); - } - std::pair parsed; if (!at::response::parseQCFG_IMS(response, parsed)) { - throw std::runtime_error("[VoLTE] unable to parse modem's response to QCFG_IMS"); + throw std::runtime_error("[VoLTE] unable to parse modem's response to QCFG with IMS"); } auto const &ims = parsed.first; diff --git a/module-services/service-cellular/tests/CMakeLists.txt b/module-services/service-cellular/tests/CMakeLists.txt index 3e62ee64a5534efcc7ba9a61d056ef1b40c4ffa0..76d40e65acc65509e2d70ddb3e68b04140584c1f 100644 --- a/module-services/service-cellular/tests/CMakeLists.txt +++ b/module-services/service-cellular/tests/CMakeLists.txt @@ -86,6 +86,15 @@ add_catch2_executable( module-cellular ) +add_catch2_executable( + NAME + cellular-volte-handler + SRCS + unittest_volteHandler.cpp + LIBS + module-cellular +) + add_catch2_executable( NAME cellular-DTMFCode diff --git a/module-services/service-cellular/tests/unittest_volteHandler.cpp b/module-services/service-cellular/tests/unittest_volteHandler.cpp new file mode 100644 index 0000000000000000000000000000000000000000..40e733e5310496e911ec4ec5645ae6158c8b780e --- /dev/null +++ b/module-services/service-cellular/tests/unittest_volteHandler.cpp @@ -0,0 +1,230 @@ +// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved. +// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md + +#include +#include +#include +#include +#include +#include + +using namespace at; +using namespace cellular::service; + +using CommandAndResponse = std::pair; + +namespace +{ + struct DummyModemResponseParser + { + template + bool operator()(Args &&...args) const + { + FAIL("test depends on parser, but shouldn't"); + return false; + } + }; + + struct QcfgImsThrowingParser + { + bool operator()(QcfgImsResult const &, bool) const + { + throw "IMS"; + } + }; + + template + struct QcfgImsDummyParser + { + bool operator()(QcfgImsResult const &, bool) const + { + return ReturnValue; + } + }; + + std::string extractCommandMnemonic(std::string const &input) + { + std::regex const extractor("AT\\+([A-Z_]+).*"); + std::smatch pieces; + if (!std::regex_match(input, pieces, extractor) || pieces.size() != 2) { + FAIL("bad command string given"); + return ""; + } + + return pieces[1].str(); + } + + template + void push(Container &container, Item &&...items) + { + (container.push(items), ...); + } +} // namespace + +namespace at +{ + struct DummyOkResult : Result + { + DummyOkResult() : Result(Result::Code::OK, {}) + {} + }; + + struct DummyErrorResult : Result + { + DummyErrorResult() : Result(Result::Code::ERROR, {}) + {} + }; + + struct CommandSequentialChannel : ChannelMock + { + CommandSequentialChannel(std::queue &&chain) : chain_{chain} + {} + + Result cmd(Cmd const &at) + { + if (chain_.empty()) { + FAIL("all commands for channel already done"); + } + + auto const ¤tCommand = chain_.front(); + auto currentCommandMnemonic = + (currentCommand.first == AT::QCFG_IMS) ? "QCFG" : magic_enum::enum_name(currentCommand.first); + if (currentCommandMnemonic != extractCommandMnemonic(at.getCmd())) { + FAIL("unexpected command in sequence"); + } + + auto ret = currentCommand.second; + chain_.pop(); + return ret; + } + + protected: + std::queue chain_; + }; +} // namespace at + +TEST_CASE("VoLTE handler test") +{ + SECTION("ThrowsAboutVoiceDomainFailure_When_TryingToEnableVolte_And_VoiceDomainCommandRespondsNOK") + { + std::queue chain; + push(chain, std::make_pair(AT::QNVFW, DummyErrorResult{})); + CommandSequentialChannel channel(std::move(chain)); + + VolteHandler handler; + + REQUIRE_THROWS_WITH(handler.switchVolte(channel, true), Catch::Contains("voice domain")); + } + + SECTION("ThrowsAboutMbnAutoselectFailure_When_TryingToEnableVolte_And_AutoselectCommandRespondsNOK") + { + std::queue chain; + push(chain, std::make_pair(AT::QNVFW, DummyOkResult{}), std::make_pair(AT::QMBNCFG, DummyErrorResult{})); + CommandSequentialChannel channel(std::move(chain)); + + VolteHandler handler; + + REQUIRE_THROWS_WITH(handler.switchVolte(channel, true), Catch::Contains("MBN")); + } + + SECTION("ThrowsAboutImsCheckingFailure_When_TryingToEnableVolte_And_ImsConfigurationQueryRespondsNOK") + { + std::queue chain; + push(chain, + std::make_pair(AT::QNVFW, DummyOkResult{}), + std::make_pair(AT::QMBNCFG, DummyOkResult{}), + std::make_pair(AT::QCFG_IMS, DummyErrorResult{})); + CommandSequentialChannel channel(std::move(chain)); + + VolteHandler handler; + + REQUIRE_THROWS_WITH(handler.switchVolte(channel, true), Catch::Contains("IMS")); + } + + SECTION("ThrowsAboutImsCheckingFailure_When_TryingToEnableVolte_And_CantParseImsConfigurationQuery") + { + std::queue chain; + push(chain, + std::make_pair(AT::QNVFW, DummyOkResult{}), + std::make_pair(AT::QMBNCFG, DummyOkResult{}), + std::make_pair(AT::QCFG_IMS, DummyOkResult{})); + CommandSequentialChannel channel(std::move(chain)); + + VolteHandler handler; + + REQUIRE_THROWS_WITH(handler.switchVolte(channel, true), Catch::Contains("IMS")); + } + + SECTION("ReturnsOk_When_TryingToEnableVolte_And_AlreadyEnabled") + { + std::queue chain; + push(chain, + std::make_pair(AT::QNVFW, DummyOkResult{}), + std::make_pair(AT::QMBNCFG, DummyOkResult{}), + std::make_pair(AT::QCFG_IMS, DummyOkResult{})); + CommandSequentialChannel channel(std::move(chain)); + + VolteHandler> handler; + + REQUIRE(handler.switchVolte(channel, true)); + } + + SECTION("ReturnsOk_When_TryingToDisableVolte_And_AlreadyDisabled") + { + std::queue chain; + push(chain, std::make_pair(AT::QCFG_IMS, DummyOkResult{})); + CommandSequentialChannel channel(std::move(chain)); + + VolteHandler> handler; + + REQUIRE(handler.switchVolte(channel, false)); + } + + SECTION("ReturnsFalse_When_TryingToDisableVolte_And_WasEnabledBefore") + { + std::queue chain; + push(chain, std::make_pair(AT::QCFG_IMS, DummyOkResult{}), std::make_pair(AT::QCFG_IMS, DummyOkResult{})); + CommandSequentialChannel channel(std::move(chain)); + + VolteHandler> handler; + + REQUIRE_FALSE(handler.switchVolte(channel, false)); + } + + SECTION("ReturnsFalse_When_TryingToEnableVolte_And_WasEnabledBefore") + { + std::queue chain; + push(chain, std::make_pair(AT::QCFG_IMS, DummyOkResult{}), std::make_pair(AT::QCFG_IMS, DummyOkResult{})); + CommandSequentialChannel channel(std::move(chain)); + + VolteHandler> handler; + + REQUIRE_FALSE(handler.switchVolte(channel, false)); + } + + SECTION("ThrowsAboutImsFailure_When_DisablingVolte_And_ImsSteeringCommandFailed") + { + std::queue chain; + push(chain, std::make_pair(AT::QCFG_IMS, DummyOkResult{}), std::make_pair(AT::QCFG_IMS, DummyErrorResult{})); + CommandSequentialChannel channel(std::move(chain)); + + VolteHandler> handler; + + REQUIRE_THROWS_WITH(handler.switchVolte(channel, false), Catch::Contains("fail") && Catch::Contains("IMS")); + } + + SECTION("ThrowsAboutImsFailure_When_EnablingVolte_And_ImsSteeringCommandFailed") + { + std::queue chain; + push(chain, + std::make_pair(AT::QNVFW, DummyOkResult{}), + std::make_pair(AT::QMBNCFG, DummyOkResult{}), + std::make_pair(AT::QCFG_IMS, DummyOkResult{}), + std::make_pair(AT::QCFG_IMS, DummyErrorResult{})); + CommandSequentialChannel channel(std::move(chain)); + + VolteHandler> handler; + + REQUIRE_THROWS_WITH(handler.switchVolte(channel, true), Catch::Contains("fail") && Catch::Contains("IMS")); + } +}