/******************************************************************************* * CLI - A simple command line interface. * Copyright (C) 2016-2021 Daniele Pallastrelli * * Boost Software License - Version 1.0 - August 17th, 2003 * * Permission is hereby granted, free of charge, to any person or organization * obtaining a copy of the software and accompanying documentation covered by * this license (the "Software") to use, reproduce, display, distribute, * execute, and transmit the Software, and to prepare derivative works of the * Software, and to permit third-parties to whom the Software is furnished to * do so, all subject to the following: * * The copyright notices in the Software and this entire statement, including * the above license grant, this restriction and the following disclaimer, * must be included in all copies of the Software, in whole or in part, and * all derivative works of the Software, unless such copies or derivative * works are solely in the form of machine-executable object code generated by * a source language processor. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ******************************************************************************/ #include #include "cli/cli.h" #include "cli/clifilesession.h" using namespace std; using namespace cli; using namespace cli::detail; namespace { string ExtractFirstPrompt(const stringstream& o) { auto content = o.str(); std::size_t pos = content.find_first_of('>'); return content.substr(0, pos); } string ExtractLastPrompt(const stringstream& o) { auto content = o.str(); std::size_t pos = content.find_last_of('\n'); content = content.substr(pos+1); pos = content.find_last_of('>'); return content.substr(0, pos); } /* takes cli> sub> foo bar sub> and gives foo bar */ string ExtractContent(const stringstream& o) { auto content = o.str(); // last line auto lastNL = content.find_last_of('\n'); auto lastLine = content.substr(lastNL+1); content = content.substr(0, lastNL); auto pos = content.find(lastLine); return content.substr(pos+lastLine.size()); } void UserInput(Cli& cli, stringstream& oss, const string& input) { oss.str(""); oss.clear(); stringstream iss; iss.str(input + '\n'); CliFileSession session(cli, iss, oss); session.Start(); } } // namespace BOOST_AUTO_TEST_SUITE(CliSuite) BOOST_AUTO_TEST_CASE(Basics) { auto rootMenu = make_unique("cli"); rootMenu->Insert("int_cmd", [](ostream& out, int par){ out << par << "\n"; }, "int_cmd help", {"int_par"} ); rootMenu->Insert("string_cmd", [](ostream& out, const string& par){ out << par << "\n"; }, "string_cmd help", {"string_par"} ); Cli cli(std::move(rootMenu)); stringstream oss; UserInput(cli, oss, "help"); BOOST_CHECK_EQUAL(ExtractFirstPrompt(oss), "cli"); BOOST_CHECK_EQUAL(ExtractLastPrompt(oss), "cli"); BOOST_CHECK(ExtractContent(oss).find("int_cmd") != string::npos); BOOST_CHECK(ExtractContent(oss).find("") != string::npos); BOOST_CHECK(ExtractContent(oss).find("string_cmd") != string::npos); BOOST_CHECK(ExtractContent(oss).find("") != string::npos); UserInput(cli, oss, "int_cmd 42"); BOOST_CHECK_EQUAL(ExtractFirstPrompt(oss), "cli"); BOOST_CHECK_EQUAL(ExtractLastPrompt(oss), "cli"); BOOST_CHECK_EQUAL(ExtractContent(oss), "42"); UserInput(cli, oss, "int_cmd wrong_int_parameter"); BOOST_CHECK_EQUAL(ExtractFirstPrompt(oss), "cli"); BOOST_CHECK_EQUAL(ExtractLastPrompt(oss), "cli"); BOOST_CHECK(ExtractContent(oss).find("wrong command:") != string::npos); UserInput(cli, oss, "int_cmd 42 0"); BOOST_CHECK_EQUAL(ExtractFirstPrompt(oss), "cli"); BOOST_CHECK_EQUAL(ExtractLastPrompt(oss), "cli"); BOOST_CHECK(ExtractContent(oss).find("wrong command:") != string::npos); UserInput(cli, oss, "string_cmd foo"); BOOST_CHECK_EQUAL(ExtractFirstPrompt(oss), "cli"); BOOST_CHECK_EQUAL(ExtractLastPrompt(oss), "cli"); BOOST_CHECK_EQUAL(ExtractContent(oss), "foo"); UserInput(cli, oss, R"(string_cmd "foo 'bar' \"foo\\2")"); BOOST_CHECK_EQUAL(ExtractFirstPrompt(oss), "cli"); BOOST_CHECK_EQUAL(ExtractLastPrompt(oss), "cli"); BOOST_CHECK_EQUAL(ExtractContent(oss), R"(foo 'bar' "foo\2)"); } BOOST_AUTO_TEST_CASE(parameters) { auto rootMenu = make_unique("cli"); rootMenu->Insert("char_cmd", [](ostream& out, char par){ out << par << "\n"; }, "char_cmd help", {"char_par"} ); rootMenu->Insert("unsigned_char_cmd", [](ostream& out, unsigned char par){ out << static_cast(par) << "\n"; }, "unsigned_char_cmd help", {"unsigned_char_par"} ); rootMenu->Insert("signed_char_cmd", [](ostream& out, signed char par){ out << static_cast(par) << "\n"; }, "signed_char_cmd help", {"signed_char_par"} ); rootMenu->Insert("short_cmd", [](ostream& out, short par){ out << par << "\n"; }, "short_cmd help", {"short_par"} ); rootMenu->Insert("unsigned_short_cmd", [](ostream& out, unsigned short par){ out << par << "\n"; }, "unsigned_short_cmd help", {"unsigned_short_par"} ); rootMenu->Insert("int_cmd", [](ostream& out, int par){ out << par << "\n"; }, "int_cmd help", {"int_par"} ); rootMenu->Insert("unsigned_int_cmd", [](ostream& out, unsigned int par){ out << par << "\n"; }, "unsigned_int_cmd help", {"unsigned_int_par"} ); rootMenu->Insert("long_cmd", [](ostream& out, long par){ out << par << "\n"; }, "long_cmd help", {"long_par"} ); rootMenu->Insert("unsigned_long_cmd", [](ostream& out, unsigned long par){ out << par << "\n"; }, "unsigned_long_cmd help", {"unsigned_long_par"} ); rootMenu->Insert("long_long_cmd", [](ostream& out, long long par){ out << par << "\n"; }, "long_long_cmd help", {"long_long_par"} ); rootMenu->Insert("unsigned_long_long_cmd", [](ostream& out, unsigned long long par){ out << par << "\n"; }, "unsigned_long_long_cmd help", {"unsigned_long_long_par"} ); rootMenu->Insert("float_cmd", [](ostream& out, float par){ out << par << "\n"; }, "float_cmd help", {"float_par"} ); rootMenu->Insert("double_cmd", [](ostream& out, double par){ out << par << "\n"; }, "double_cmd help", {"double_par"} ); rootMenu->Insert("long_double_cmd", [](ostream& out, long double par){ out << par << "\n"; }, "long_double_cmd help", {"long_double_par"} ); rootMenu->Insert("string_cmd", [](ostream& out, const string& par){ out << par << "\n"; }, "string_cmd help", {"string_par"} ); Cli cli(std::move(rootMenu)); stringstream oss; UserInput(cli, oss, "char_cmd a"); BOOST_CHECK_EQUAL(ExtractContent(oss), "a"); UserInput(cli, oss, "char_cmd ' '"); BOOST_CHECK_EQUAL(ExtractContent(oss), " "); UserInput(cli, oss, "char_cmd aa"); BOOST_CHECK(ExtractContent(oss).find("wrong command:") != string::npos); UserInput(cli, oss, "unsigned_char_cmd 42"); BOOST_CHECK_EQUAL(ExtractContent(oss), "42"); UserInput(cli, oss, "unsigned_char_cmd -42"); BOOST_CHECK(ExtractContent(oss).find("wrong command:") != string::npos); UserInput(cli, oss, "unsigned_char_cmd a"); BOOST_CHECK(ExtractContent(oss).find("wrong command:") != string::npos); UserInput(cli, oss, "unsigned_char_cmd 99999999999999999999999999999999999999999"); BOOST_CHECK(ExtractContent(oss).find("wrong command:") != string::npos); UserInput(cli, oss, "signed_char_cmd 42"); BOOST_CHECK_EQUAL(ExtractContent(oss), "42"); UserInput(cli, oss, "signed_char_cmd -42"); BOOST_CHECK_EQUAL(ExtractContent(oss), "-42"); UserInput(cli, oss, "signed_char_cmd a"); BOOST_CHECK(ExtractContent(oss).find("wrong command:") != string::npos); UserInput(cli, oss, "signed_char_cmd 99999999999999999999999999999999999999999"); BOOST_CHECK(ExtractContent(oss).find("wrong command:") != string::npos); UserInput(cli, oss, "short_cmd 42"); BOOST_CHECK_EQUAL(ExtractContent(oss), "42"); UserInput(cli, oss, "short_cmd -42"); BOOST_CHECK_EQUAL(ExtractContent(oss), "-42"); UserInput(cli, oss, "short_cmd a"); BOOST_CHECK(ExtractContent(oss).find("wrong command:") != string::npos); UserInput(cli, oss, "short_cmd 99999999999999999999999999999999999999999"); BOOST_CHECK(ExtractContent(oss).find("wrong command:") != string::npos); UserInput(cli, oss, "unsigned_short_cmd 42"); BOOST_CHECK_EQUAL(ExtractContent(oss), "42"); UserInput(cli, oss, "unsigned_short_cmd -42"); BOOST_CHECK(ExtractContent(oss).find("wrong command:") != string::npos); UserInput(cli, oss, "unsigned_short_cmd a"); BOOST_CHECK(ExtractContent(oss).find("wrong command:") != string::npos); UserInput(cli, oss, "unsigned_short_cmd 99999999999999999999999999999999999999999"); BOOST_CHECK(ExtractContent(oss).find("wrong command:") != string::npos); UserInput(cli, oss, "int_cmd 42"); BOOST_CHECK_EQUAL(ExtractContent(oss), "42"); UserInput(cli, oss, "int_cmd -42"); BOOST_CHECK_EQUAL(ExtractContent(oss), "-42"); UserInput(cli, oss, "int_cmd a"); BOOST_CHECK(ExtractContent(oss).find("wrong command:") != string::npos); UserInput(cli, oss, "int_cmd 99999999999999999999999999999999999999999"); BOOST_CHECK(ExtractContent(oss).find("wrong command:") != string::npos); UserInput(cli, oss, "unsigned_int_cmd 42"); BOOST_CHECK_EQUAL(ExtractContent(oss), "42"); UserInput(cli, oss, "unsigned_int_cmd -42"); BOOST_CHECK(ExtractContent(oss).find("wrong command:") != string::npos); UserInput(cli, oss, "unsigned_int_cmd a"); BOOST_CHECK(ExtractContent(oss).find("wrong command:") != string::npos); UserInput(cli, oss, "unsigned_int_cmd 99999999999999999999999999999999999999999"); BOOST_CHECK(ExtractContent(oss).find("wrong command:") != string::npos); UserInput(cli, oss, "long_cmd 42"); BOOST_CHECK_EQUAL(ExtractContent(oss), "42"); UserInput(cli, oss, "long_cmd -42"); BOOST_CHECK_EQUAL(ExtractContent(oss), "-42"); UserInput(cli, oss, "long_cmd a"); BOOST_CHECK(ExtractContent(oss).find("wrong command:") != string::npos); UserInput(cli, oss, "unsigned_long_cmd 42"); BOOST_CHECK_EQUAL(ExtractContent(oss), "42"); UserInput(cli, oss, "unsigned_long_cmd -42"); BOOST_CHECK(ExtractContent(oss).find("wrong command:") != string::npos); UserInput(cli, oss, "unsigned_long_cmd a"); BOOST_CHECK(ExtractContent(oss).find("wrong command:") != string::npos); UserInput(cli, oss, "long_long_cmd 42"); BOOST_CHECK_EQUAL(ExtractContent(oss), "42"); UserInput(cli, oss, "long_long_cmd -42"); BOOST_CHECK_EQUAL(ExtractContent(oss), "-42"); UserInput(cli, oss, "long_long_cmd a"); BOOST_CHECK(ExtractContent(oss).find("wrong command:") != string::npos); UserInput(cli, oss, "unsigned_long_long_cmd 42"); BOOST_CHECK_EQUAL(ExtractContent(oss), "42"); UserInput(cli, oss, "unsigned_long_long_cmd -42"); BOOST_CHECK(ExtractContent(oss).find("wrong command:") != string::npos); UserInput(cli, oss, "unsigned_long_long_cmd a"); BOOST_CHECK(ExtractContent(oss).find("wrong command:") != string::npos); UserInput(cli, oss, "float_cmd 0.1"); BOOST_CHECK_EQUAL(ExtractContent(oss), "0.1"); UserInput(cli, oss, "float_cmd a"); BOOST_CHECK(ExtractContent(oss).find("wrong command:") != string::npos); UserInput(cli, oss, "double_cmd 0.1"); BOOST_CHECK_EQUAL(ExtractContent(oss), "0.1"); UserInput(cli, oss, "double_cmd a"); BOOST_CHECK(ExtractContent(oss).find("wrong command:") != string::npos); UserInput(cli, oss, "long_double_cmd 0.1"); BOOST_CHECK_EQUAL(ExtractContent(oss), "0.1"); UserInput(cli, oss, "long_double_cmd a"); BOOST_CHECK(ExtractContent(oss).find("wrong command:") != string::npos); } BOOST_AUTO_TEST_CASE(freeform) { auto rootMenu = make_unique("cli"); rootMenu->Insert("cmd_printer_by_ref", [](ostream& out, const std::vector& args){ for (const auto& entry : args) { out << entry << "*"; } out << "\n"; }, "cmd_printer help", {""} ); rootMenu->Insert("cmd_printer_by_value", [](ostream& out, std::vector args){ for (const auto& entry : args) { out << entry << "*"; } out << "\n"; }, "cmd_printer help", {""} ); Cli cli(std::move(rootMenu)); stringstream oss; UserInput(cli, oss, R"(cmd_printer_by_value a b 'c d e' f)"); BOOST_CHECK_EQUAL(ExtractFirstPrompt(oss), "cli"); BOOST_CHECK_EQUAL(ExtractLastPrompt(oss), "cli"); BOOST_CHECK_EQUAL(ExtractContent(oss), "a*b*c d e*f*"); UserInput(cli, oss, R"(cmd_printer_by_ref a b 'c d e' f)"); BOOST_CHECK_EQUAL(ExtractFirstPrompt(oss), "cli"); BOOST_CHECK_EQUAL(ExtractLastPrompt(oss), "cli"); BOOST_CHECK_EQUAL(ExtractContent(oss), "a*b*c d e*f*"); // empty parameters UserInput(cli, oss, R"(cmd_printer_by_value)"); BOOST_CHECK_EQUAL(ExtractFirstPrompt(oss), "cli"); BOOST_CHECK_EQUAL(ExtractLastPrompt(oss), "cli"); BOOST_CHECK_EQUAL(ExtractContent(oss), ""); } BOOST_AUTO_TEST_CASE(borderLine) { auto rootMenu = make_unique("cli"); Cli cli(std::move(rootMenu)); stringstream oss; UserInput(cli, oss, ""); BOOST_CHECK_EQUAL(oss.str(), "cli> cli> "); UserInput(cli, oss, "\t"); BOOST_CHECK_EQUAL(oss.str(), "cli> cli> "); } BOOST_AUTO_TEST_CASE(Submenus) { auto rootMenu = make_unique("cli"); auto subMenu = make_unique("sub"); subMenu->Insert("int_cmd", [](ostream& out, int par){ out << par << "\n"; }, "int_cmd help", {"int_par"} ); subMenu->Insert("string_cmd", [](ostream& out, const string& par){ out << par << "\n"; }, "string_cmd help", {"string_par"} ); auto subSubMenu = make_unique("subsub"); subSubMenu->Insert("double_int_cmd", [](ostream& out, int par1, int par2){ out << par1 << par2 << "\n"; } ); subSubMenu->Insert("double_string_cmd", [](ostream& out, const string& par1, const string& par2){ out << par1 << par2 << "\n"; } ); subMenu->Insert(std::move(subSubMenu)); rootMenu->Insert(std::move(subMenu)); Cli cli(std::move(rootMenu)); stringstream oss; UserInput(cli, oss, "help"); BOOST_CHECK_EQUAL(ExtractFirstPrompt(oss), "cli"); BOOST_CHECK_EQUAL(ExtractLastPrompt(oss), "cli"); BOOST_CHECK(ExtractContent(oss).find("sub") != string::npos); BOOST_CHECK(ExtractContent(oss).find("help") != string::npos); BOOST_CHECK(ExtractContent(oss).find("exit") != string::npos); // from the root menu UserInput(cli, oss, "sub int_cmd 42"); BOOST_CHECK_EQUAL(ExtractFirstPrompt(oss), "cli"); BOOST_CHECK_EQUAL(ExtractLastPrompt(oss), "cli"); BOOST_CHECK_EQUAL(ExtractContent(oss), "42"); UserInput(cli, oss, "sub string_cmd foo"); BOOST_CHECK_EQUAL(ExtractFirstPrompt(oss), "cli"); BOOST_CHECK_EQUAL(ExtractLastPrompt(oss), "cli"); BOOST_CHECK_EQUAL(ExtractContent(oss), "foo"); UserInput(cli, oss, "sub subsub double_int_cmd 42 0"); BOOST_CHECK_EQUAL(ExtractFirstPrompt(oss), "cli"); BOOST_CHECK_EQUAL(ExtractLastPrompt(oss), "cli"); BOOST_CHECK_EQUAL(ExtractContent(oss), "420"); UserInput(cli, oss, "sub subsub double_string_cmd foo bar"); BOOST_CHECK_EQUAL(ExtractFirstPrompt(oss), "cli"); BOOST_CHECK_EQUAL(ExtractLastPrompt(oss), "cli"); BOOST_CHECK_EQUAL(ExtractContent(oss), "foobar"); // enter the sub menu UserInput(cli, oss, "sub\nint_cmd 42"); BOOST_CHECK_EQUAL(ExtractLastPrompt(oss), "sub"); BOOST_CHECK_EQUAL(ExtractContent(oss), "42"); UserInput(cli, oss, "sub\nstring_cmd foo"); BOOST_CHECK_EQUAL(ExtractLastPrompt(oss), "sub"); BOOST_CHECK_EQUAL(ExtractContent(oss), "foo"); } BOOST_AUTO_TEST_CASE(EnterActions) { auto rootMenu = make_unique("cli"); rootMenu->Insert("int_cmd", [](ostream &out, int par) { out << par << "\n"; }, "int_cmd help", {"int_par"}); rootMenu->Insert( "string_cmd", [](ostream &out, const string &par) { out << par << "\n"; }, "string_cmd help", {"string_par"}); Cli cli(std::move(rootMenu)); bool enterActionDone = false; cli.EnterAction( [&enterActionDone](std::ostream &) noexcept { enterActionDone = true; }); stringstream oss; UserInput(cli, oss, "exit"); BOOST_CHECK(enterActionDone); } BOOST_AUTO_TEST_CASE(ExitActions) { auto rootMenu = make_unique("cli"); rootMenu->Insert("int_cmd", [](ostream& out, int par){ out << par << "\n"; }, "int_cmd help", {"int_par"} ); rootMenu->Insert("string_cmd", [](ostream& out, const string& par){ out << par << "\n"; }, "string_cmd help", {"string_par"} ); Cli cli(std::move(rootMenu)); bool exitActionDone = false; cli.ExitAction([&](std::ostream&) noexcept { exitActionDone=true; }); stringstream oss; UserInput(cli, oss, "exit"); BOOST_CHECK(exitActionDone); } BOOST_AUTO_TEST_CASE(Exceptions) { auto rootMenu = make_unique("cli"); rootMenu->Insert("stdexception", [](ostream&){ throw std::logic_error("myerror"); } ); rootMenu->Insert("customexception", [](ostream&){ throw 42; } ); Cli cli(std::move(rootMenu)); stringstream oss; // std exception type, no custom handler BOOST_CHECK_NO_THROW( UserInput(cli, oss, "stdexception") ); BOOST_CHECK_EQUAL(ExtractContent(oss), "myerror"); // std exception type, custom handler bool excActionDone = false; cli.StdExceptionHandler( [&](std::ostream&, const std::string&, const std::exception&) noexcept { excActionDone = true; } ); BOOST_CHECK_NO_THROW( UserInput(cli, oss, "stdexception") ); BOOST_CHECK(excActionDone); // custom exception BOOST_CHECK_NO_THROW( UserInput(cli, oss, "customexception") ); } BOOST_AUTO_TEST_SUITE_END()