diff --git a/src/utils/Console.cpp b/src/utils/Console.cpp index a3f65220..a941695b 100644 --- a/src/utils/Console.cpp +++ b/src/utils/Console.cpp @@ -11,6 +11,7 @@ using std::flush; using std::getline; using boost::optional; using boost::none; +using std::function; IOStreamConsole::IOStreamConsole(): IOStreamConsole(std::cout, std::cin) { } @@ -34,6 +35,32 @@ optional parseInt(const string &str) { } } +function(const std::string &input)> parseUIntWithMinMax(unsigned int min, unsigned int max) { + return [min, max] (const string &input) { + optional parsed = parseInt(input); + if (parsed == none) { + return optional(none); + } + unsigned int value = static_cast(*parsed); + if (value < min || value > max) { + return optional(none); + } + return optional(value); + }; +} + +template +Return IOStreamConsole::_askForChoice(const string &question, function (const string&)> parse) { + optional choice = none; + do { + _output << question << flush; + string choiceStr; + getline(_input, choiceStr); + choice = parse(choiceStr); + } while(choice == none); + return *choice; +} + unsigned int IOStreamConsole::ask(const string &question, const vector &options) { if(options.size() == 0) { throw std::invalid_argument("options should have at least one entry"); @@ -42,14 +69,27 @@ unsigned int IOStreamConsole::ask(const string &question, const vector & for (unsigned int i = 0; i < options.size(); ++i) { _output << " [" << (i+1) << "] " << options[i] << "\n"; } - optional choice; - do { - _output << "Your choice [1-" << options.size() << "]: " << flush; - string choiceStr; - getline(_input, choiceStr); - choice = parseInt(choiceStr); - } while(choice == none || *choice < 1 || static_cast(*choice) > options.size()); - return *choice-1; + int choice = _askForChoice("Your choice [1-" + std::to_string(options.size()) + "]: ", parseUIntWithMinMax(1, options.size())); + return choice-1; +} + +function(const string &input)> parseYesNo() { + return [] (const string &input) { + string trimmed = input; + boost::algorithm::trim(trimmed); + if(trimmed == "Y" || trimmed == "y" || trimmed == "Yes" || trimmed == "yes") { + return optional(true); + } else if (trimmed == "N" || trimmed == "n" || trimmed == "No" || trimmed == "no") { + return optional(false); + } else { + return optional(none); + } + }; +} + +bool IOStreamConsole::askYesNo(const string &question) { + _output << question << "\n"; + return _askForChoice("Your choice [y/n]: ", parseYesNo()); } void IOStreamConsole::print(const std::string &output) { diff --git a/src/utils/Console.h b/src/utils/Console.h index 68410db1..76fa6047 100644 --- a/src/utils/Console.h +++ b/src/utils/Console.h @@ -5,10 +5,12 @@ #include #include #include +#include class Console { public: virtual unsigned int ask(const std::string &question, const std::vector &options) = 0; + virtual bool askYesNo(const std::string &question) = 0; virtual void print(const std::string &output) = 0; }; @@ -17,8 +19,12 @@ public: IOStreamConsole(); IOStreamConsole(std::ostream &output, std::istream &input); unsigned int ask(const std::string &question, const std::vector &options) override; + bool askYesNo(const std::string &question) override; void print(const std::string &output) override; private: + template + Return _askForChoice(const std::string &question, std::function (const std::string&)> parse); + std::ostream &_output; std::istream &_input; }; diff --git a/test/CryFsTest.cpp b/test/CryFsTest.cpp index ebff606b..3e787cd7 100644 --- a/test/CryFsTest.cpp +++ b/test/CryFsTest.cpp @@ -26,6 +26,9 @@ class MockConsole: public Console { unsigned int ask(const std::string &, const std::vector &) override { return 0; } + bool askYesNo(const std::string &) override { + return true; + } }; class CryFsTest: public Test { diff --git a/test/utils/ConsoleTest.h b/test/utils/ConsoleTest.h new file mode 100644 index 00000000..00a29cfa --- /dev/null +++ b/test/utils/ConsoleTest.h @@ -0,0 +1,77 @@ +#ifndef CRYFS_CONSOLETEST_H +#define CRYFS_CONSOLETEST_H + +#include + +#include "../../src/utils/Console.h" + +#include +#include +#include + +class ConsoleThread { +public: + ConsoleThread(std::ostream &ostr, std::istream &istr): _console(ostr, istr) {} + std::future ask(const std::string &question, const std::vector &options) { + return std::async(std::launch::async, [this, question, options]() { + return _console.ask(question, options); + }); + } + std::future askYesNo(const std::string &question) { + return std::async(std::launch::async, [this, question]() { + return _console.askYesNo(question); + }); + } + void print(const std::string &output) { + _console.print(output); + } +private: + IOStreamConsole _console; +}; + +class ConsoleTest: public ::testing::Test { +public: + ConsoleTest(): _inputStr(), _outputStr(), _input(&_inputStr), _output(&_outputStr), _console(_output, _input) {} + + void EXPECT_OUTPUT_LINES(std::initializer_list lines) { + for (const std::string &line : lines) { + EXPECT_OUTPUT_LINE(line); + } + } + + void EXPECT_OUTPUT_LINE(const std::string &expected, char delimiter = '\n', const std::string &expected_after_delimiter = "") { + std::string actual; + std::getline(_output, actual, delimiter); + EXPECT_EQ(expected, actual); + for (char expected_char : expected_after_delimiter) { + char actual_char; + _output.get(actual_char); + EXPECT_EQ(expected_char, actual_char); + } + } + + void sendInputLine(const std::string &line) { + _input << line << "\n" << std::flush; + } + + std::future ask(const std::string &question, const std::vector &options) { + return _console.ask(question, options); + } + + std::future askYesNo(const std::string &question) { + return _console.askYesNo(question); + } + + void print(const std::string &output) { + _console.print(output); + } + +private: + cpputils::pipestream _inputStr; + cpputils::pipestream _outputStr; + std::iostream _input; + std::iostream _output; + ConsoleThread _console; +}; + +#endif diff --git a/test/utils/ConsoleTest.cpp b/test/utils/ConsoleTest_Ask.cpp similarity index 64% rename from test/utils/ConsoleTest.cpp rename to test/utils/ConsoleTest_Ask.cpp index e2ea2e8e..dc828605 100644 --- a/test/utils/ConsoleTest.cpp +++ b/test/utils/ConsoleTest_Ask.cpp @@ -1,10 +1,4 @@ -#include - -#include "../../src/utils/Console.h" - -#include -#include -#include +#include "ConsoleTest.h" using std::stringstream; using std::string; @@ -14,70 +8,16 @@ using std::ostream; using std::future; using std::initializer_list; -class ConsoleThread { -public: - ConsoleThread(ostream &ostr, istream &istr): _console(ostr, istr) {} - future ask(const string &question, const vector &options) { - return std::async(std::launch::async, [this, question, options]() { - return _console.ask(question, options); - }); - } - void print(const string &output) { - _console.print(output); - } -private: - IOStreamConsole _console; -}; +class ConsoleTest_Ask: public ConsoleTest {}; -class ConsoleTest: public ::testing::Test { -public: - ConsoleTest(): _inputStr(), _outputStr(), _input(&_inputStr), _output(&_outputStr), _console(_output, _input) {} - - void EXPECT_OUTPUT_LINES(initializer_list lines) { - for (const string &line : lines) { - EXPECT_OUTPUT_LINE(line); - } - } - - void EXPECT_OUTPUT_LINE(const string &expected, char delimiter = '\n', const string &expected_after_delimiter = "") { - string actual; - std::getline(_output, actual, delimiter); - EXPECT_EQ(expected, actual); - for (char expected_char : expected_after_delimiter) { - char actual_char; - _output.get(actual_char); - EXPECT_EQ(expected_char, actual_char); - } - } - - void sendInputLine(const string &line) { - _input << line << "\n" << std::flush; - } - - future ask(const string &question, const vector &options) { - return _console.ask(question, options); - } - - void print(const string &output) { - _console.print(output); - } - -private: - cpputils::pipestream _inputStr; - cpputils::pipestream _outputStr; - std::iostream _input; - std::iostream _output; - ConsoleThread _console; -}; - -TEST_F(ConsoleTest, AskCrashesWithoutOptions) { +TEST_F(ConsoleTest_Ask, CrashesWithoutOptions) { EXPECT_THROW( (ask("My Question?", {}).get()), std::invalid_argument ); } -TEST_F(ConsoleTest, AskOneOption) { +TEST_F(ConsoleTest_Ask, OneOption) { auto chosen = ask("My Question?", {"First Option"}); EXPECT_OUTPUT_LINES({ "My Question?", @@ -88,7 +28,7 @@ TEST_F(ConsoleTest, AskOneOption) { EXPECT_EQ(0, chosen.get()); } -TEST_F(ConsoleTest, AskTwoOptions_ChooseFirst) { +TEST_F(ConsoleTest_Ask, TwoOptions_ChooseFirst) { auto chosen = ask("My Question?", {"First Option", "Second Option"}); EXPECT_OUTPUT_LINES({ "My Question?", @@ -100,7 +40,7 @@ TEST_F(ConsoleTest, AskTwoOptions_ChooseFirst) { EXPECT_EQ(0, chosen.get()); } -TEST_F(ConsoleTest, AskTwoOptions_ChooseSecond) { +TEST_F(ConsoleTest_Ask, TwoOptions_ChooseSecond) { auto chosen = ask("My Question?", {"First Option", "Second Option"}); EXPECT_OUTPUT_LINES({ "My Question?", @@ -112,7 +52,7 @@ TEST_F(ConsoleTest, AskTwoOptions_ChooseSecond) { EXPECT_EQ(1, chosen.get()); } -TEST_F(ConsoleTest, AskThreeOptions_ChooseFirst) { +TEST_F(ConsoleTest_Ask, ThreeOptions_ChooseFirst) { auto chosen = ask("My Other Question?", {"1st Option", "2nd Option", "3rd Option"}); EXPECT_OUTPUT_LINES({ "My Other Question?", @@ -125,7 +65,7 @@ TEST_F(ConsoleTest, AskThreeOptions_ChooseFirst) { EXPECT_EQ(0, chosen.get()); } -TEST_F(ConsoleTest, AskThreeOptions_ChooseSecond) { +TEST_F(ConsoleTest_Ask, ThreeOptions_ChooseSecond) { auto chosen = ask("My Question?", {"1st Option", "2nd Option", "3rd Option"}); EXPECT_OUTPUT_LINES({ "My Question?", @@ -138,7 +78,7 @@ TEST_F(ConsoleTest, AskThreeOptions_ChooseSecond) { EXPECT_EQ(1, chosen.get()); } -TEST_F(ConsoleTest, AskThreeOptions_ChooseThird) { +TEST_F(ConsoleTest_Ask, ThreeOptions_ChooseThird) { auto chosen = ask("My Question?", {"1st Option", "2nd Option", "3rd Option"}); EXPECT_OUTPUT_LINES({ "My Question?", @@ -151,7 +91,7 @@ TEST_F(ConsoleTest, AskThreeOptions_ChooseThird) { EXPECT_EQ(2, chosen.get()); } -TEST_F(ConsoleTest, InputWithLeadingSpaces) { +TEST_F(ConsoleTest_Ask, InputWithLeadingSpaces) { auto chosen = ask("My Question?", {"First Option", "Second Option"}); EXPECT_OUTPUT_LINES({ "My Question?", @@ -163,7 +103,7 @@ TEST_F(ConsoleTest, InputWithLeadingSpaces) { EXPECT_EQ(1, chosen.get()); } -TEST_F(ConsoleTest, InputWithFollowingSpaces) { +TEST_F(ConsoleTest_Ask, InputWithFollowingSpaces) { auto chosen = ask("My Question?", {"First Option", "Second Option"}); EXPECT_OUTPUT_LINES({ "My Question?", @@ -175,7 +115,7 @@ TEST_F(ConsoleTest, InputWithFollowingSpaces) { EXPECT_EQ(1, chosen.get()); } -TEST_F(ConsoleTest, InputWithLeadingAndFollowingSpaces) { +TEST_F(ConsoleTest_Ask, InputWithLeadingAndFollowingSpaces) { auto chosen = ask("My Question?", {"First Option", "Second Option"}); EXPECT_OUTPUT_LINES({ "My Question?", @@ -187,7 +127,7 @@ TEST_F(ConsoleTest, InputWithLeadingAndFollowingSpaces) { EXPECT_EQ(1, chosen.get()); } -TEST_F(ConsoleTest, InputEmptyLine) { +TEST_F(ConsoleTest_Ask, InputEmptyLine) { auto chosen = ask("My Question?", {"First Option", "Second Option"}); EXPECT_OUTPUT_LINES({ "My Question?", @@ -203,7 +143,7 @@ TEST_F(ConsoleTest, InputEmptyLine) { EXPECT_EQ(1, chosen.get()); } -TEST_F(ConsoleTest, InputWrongNumbers) { +TEST_F(ConsoleTest_Ask, InputWrongNumbers) { auto chosen = ask("My Question?", {"1st Option", "2nd Option"}); EXPECT_OUTPUT_LINES({ "My Question?", @@ -225,7 +165,7 @@ TEST_F(ConsoleTest, InputWrongNumbers) { EXPECT_EQ(1, chosen.get()); } -TEST_F(ConsoleTest, InputNonNumbers) { +TEST_F(ConsoleTest_Ask, InputNonNumbers) { auto chosen = ask("My Question?", {"1st Option", "2nd Option"}); EXPECT_OUTPUT_LINES({ "My Question?", @@ -245,9 +185,4 @@ TEST_F(ConsoleTest, InputNonNumbers) { EXPECT_OUTPUT_LINE("Your choice [1-2]", ':', " "); sendInputLine("2"); EXPECT_EQ(1, chosen.get()); -} - -TEST_F(ConsoleTest, TestPrint) { - print("Bla Blub"); - EXPECT_OUTPUT_LINE("Bla Blu", 'b'); // 'b' is the delimiter for reading } \ No newline at end of file diff --git a/test/utils/ConsoleTest_AskYesNo.cpp b/test/utils/ConsoleTest_AskYesNo.cpp new file mode 100644 index 00000000..b1da6a55 --- /dev/null +++ b/test/utils/ConsoleTest_AskYesNo.cpp @@ -0,0 +1,108 @@ +#include "ConsoleTest.h" + +using std::string; + +class ConsoleTest_AskYesNo: public ConsoleTest { +public: + void EXPECT_TRUE_ON_INPUT(const string &input) { + EXPECT_RESULT_ON_INPUT(true, input); + } + + void EXPECT_FALSE_ON_INPUT(const string &input) { + EXPECT_RESULT_ON_INPUT(false, input); + } + + void EXPECT_RESULT_ON_INPUT(const bool expected, const string &input) { + auto chosen = askYesNo("Are you sure blablub?"); + EXPECT_OUTPUT_LINES({"Are you sure blablub?"}); + EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " "); + sendInputLine(input); + EXPECT_EQ(expected, chosen.get()); + } +}; + +TEST_F(ConsoleTest_AskYesNo, Input_Yes) { + EXPECT_TRUE_ON_INPUT("Yes"); +} + +TEST_F(ConsoleTest_AskYesNo, Input_yes) { + EXPECT_TRUE_ON_INPUT("yes"); +} + +TEST_F(ConsoleTest_AskYesNo, Input_Y) { + EXPECT_TRUE_ON_INPUT("Y"); +} + +TEST_F(ConsoleTest_AskYesNo, Input_y) { + EXPECT_TRUE_ON_INPUT("y"); +} + +TEST_F(ConsoleTest_AskYesNo, Input_No) { + EXPECT_FALSE_ON_INPUT("No"); +} + +TEST_F(ConsoleTest_AskYesNo, Input_no) { + EXPECT_FALSE_ON_INPUT("no"); +} + +TEST_F(ConsoleTest_AskYesNo, Input_N) { + EXPECT_FALSE_ON_INPUT("N"); +} + +TEST_F(ConsoleTest_AskYesNo, Input_n) { + EXPECT_FALSE_ON_INPUT("n"); +} + +TEST_F(ConsoleTest_AskYesNo, InputWithLeadingSpaces) { + EXPECT_TRUE_ON_INPUT(" y"); +} + +TEST_F(ConsoleTest_AskYesNo, InputWithFollowingSpaces) { + EXPECT_TRUE_ON_INPUT("y "); +} + +TEST_F(ConsoleTest_AskYesNo, InputWithLeadingAndFollowingSpaces) { + EXPECT_TRUE_ON_INPUT(" y "); +} + +TEST_F(ConsoleTest_AskYesNo, InputEmptyLine) { + auto chosen = askYesNo("My Question?"); + EXPECT_OUTPUT_LINES({"My Question?"}); + EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " "); + sendInputLine(""); + EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " "); + sendInputLine(" "); // empty line with space + EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " "); + sendInputLine("y"); + EXPECT_EQ(true, chosen.get()); +} + +TEST_F(ConsoleTest_AskYesNo, WrongInput) { + auto chosen = askYesNo("My Question?"); + EXPECT_OUTPUT_LINES({"My Question?"}); + EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " "); + sendInputLine("0"); + EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " "); + sendInputLine("1"); + EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " "); + sendInputLine("bla"); + EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " "); + sendInputLine("Y_andsomethingelse"); + EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " "); + sendInputLine("y_andsomethingelse"); + EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " "); + sendInputLine("N_andsomethingelse"); + EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " "); + sendInputLine("n_andsomethingelse"); + EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " "); + sendInputLine("Yes_andsomethingelse"); + EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " "); + sendInputLine("yes_andsomethingelse"); + EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " "); + sendInputLine("No_andsomethingelse"); + EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " "); + sendInputLine("no_andsomethingelse"); + EXPECT_OUTPUT_LINE("Your choice [y/n]", ':', " "); + sendInputLine("y"); + EXPECT_EQ(true, chosen.get()); +} diff --git a/test/utils/ConsoleTest_Print.cpp b/test/utils/ConsoleTest_Print.cpp new file mode 100644 index 00000000..e38585ee --- /dev/null +++ b/test/utils/ConsoleTest_Print.cpp @@ -0,0 +1,6 @@ +#include "ConsoleTest.h" + +TEST_F(ConsoleTest, Print) { + print("Bla Blub"); + EXPECT_OUTPUT_LINE("Bla Blu", 'b'); // 'b' is the delimiter for reading +}