Console class supports askYesNo(question)

This commit is contained in:
Sebastian Messmer 2015-09-04 16:00:41 +02:00
parent 218463cf91
commit a9a5a5d04d
7 changed files with 263 additions and 88 deletions

View File

@ -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<int> parseInt(const string &str) {
}
}
function<optional<unsigned int>(const std::string &input)> parseUIntWithMinMax(unsigned int min, unsigned int max) {
return [min, max] (const string &input) {
optional<int> parsed = parseInt(input);
if (parsed == none) {
return optional<unsigned int>(none);
}
unsigned int value = static_cast<unsigned int>(*parsed);
if (value < min || value > max) {
return optional<unsigned int>(none);
}
return optional<unsigned int>(value);
};
}
template<typename Return>
Return IOStreamConsole::_askForChoice(const string &question, function<optional<Return> (const string&)> parse) {
optional<Return> 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<string> &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<string> &
for (unsigned int i = 0; i < options.size(); ++i) {
_output << " [" << (i+1) << "] " << options[i] << "\n";
}
optional<int> choice;
do {
_output << "Your choice [1-" << options.size() << "]: " << flush;
string choiceStr;
getline(_input, choiceStr);
choice = parseInt(choiceStr);
} while(choice == none || *choice < 1 || static_cast<unsigned int>(*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<optional<bool>(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<bool>(true);
} else if (trimmed == "N" || trimmed == "n" || trimmed == "No" || trimmed == "no") {
return optional<bool>(false);
} else {
return optional<bool>(none);
}
};
}
bool IOStreamConsole::askYesNo(const string &question) {
_output << question << "\n";
return _askForChoice("Your choice [y/n]: ", parseYesNo());
}
void IOStreamConsole::print(const std::string &output) {

View File

@ -5,10 +5,12 @@
#include <string>
#include <vector>
#include <iostream>
#include <boost/optional.hpp>
class Console {
public:
virtual unsigned int ask(const std::string &question, const std::vector<std::string> &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<std::string> &options) override;
bool askYesNo(const std::string &question) override;
void print(const std::string &output) override;
private:
template<typename Return>
Return _askForChoice(const std::string &question, std::function<boost::optional<Return> (const std::string&)> parse);
std::ostream &_output;
std::istream &_input;
};

View File

@ -26,6 +26,9 @@ class MockConsole: public Console {
unsigned int ask(const std::string &, const std::vector<std::string> &) override {
return 0;
}
bool askYesNo(const std::string &) override {
return true;
}
};
class CryFsTest: public Test {

77
test/utils/ConsoleTest.h Normal file
View File

@ -0,0 +1,77 @@
#ifndef CRYFS_CONSOLETEST_H
#define CRYFS_CONSOLETEST_H
#include <google/gtest/gtest.h>
#include "../../src/utils/Console.h"
#include <future>
#include <thread>
#include <messmer/cpp-utils/pipestream.h>
class ConsoleThread {
public:
ConsoleThread(std::ostream &ostr, std::istream &istr): _console(ostr, istr) {}
std::future<unsigned int> ask(const std::string &question, const std::vector<std::string> &options) {
return std::async(std::launch::async, [this, question, options]() {
return _console.ask(question, options);
});
}
std::future<bool> 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<std::string> 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<unsigned int> ask(const std::string &question, const std::vector<std::string> &options) {
return _console.ask(question, options);
}
std::future<bool> 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

View File

@ -1,10 +1,4 @@
#include <google/gtest/gtest.h>
#include "../../src/utils/Console.h"
#include <future>
#include <thread>
#include <messmer/cpp-utils/pipestream.h>
#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<unsigned int> ask(const string &question, const vector<string> &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<string> 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<unsigned int> ask(const string &question, const vector<string> &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
}

View File

@ -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());
}

View File

@ -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
}