#include "app_helper.hpp" #include "gmock/gmock.h" #include using ::testing::HasSubstr; using cx = std::complex; CLI::Option * add_option(CLI::App &app, std::string name, cx &variable, std::string description = "", bool defaulted = false) { CLI::callback_t fun = [&variable](CLI::results_t res) { double x, y; bool worked = CLI::detail::lexical_cast(res[0], x) && CLI::detail::lexical_cast(res[1], y); if(worked) variable = cx(x, y); return worked; }; CLI::Option *opt = app.add_option(name, fun, description, defaulted); opt->type_name("COMPLEX")->type_size(2); if(defaulted) { std::stringstream out; out << variable; opt->default_str(out.str()); } return opt; } TEST_F(TApp, AddingComplexParser) { cx comp{0, 0}; add_option(app, "-c,--complex", comp); args = {"-c", "1.5", "2.5"}; run(); EXPECT_DOUBLE_EQ(1.5, comp.real()); EXPECT_DOUBLE_EQ(2.5, comp.imag()); } TEST_F(TApp, DefaultComplex) { cx comp{1, 2}; add_option(app, "-c,--complex", comp, "", true); args = {"-c", "4", "3"}; std::string help = app.help(); EXPECT_THAT(help, HasSubstr("1")); EXPECT_THAT(help, HasSubstr("2")); EXPECT_DOUBLE_EQ(1, comp.real()); EXPECT_DOUBLE_EQ(2, comp.imag()); run(); EXPECT_DOUBLE_EQ(4, comp.real()); EXPECT_DOUBLE_EQ(3, comp.imag()); } TEST_F(TApp, BuiltinComplex) { cx comp{1, 2}; app.add_complex("-c,--complex", comp, "", true); args = {"-c", "4", "3"}; std::string help = app.help(); EXPECT_THAT(help, HasSubstr("1")); EXPECT_THAT(help, HasSubstr("2")); EXPECT_THAT(help, HasSubstr("COMPLEX")); EXPECT_DOUBLE_EQ(1, comp.real()); EXPECT_DOUBLE_EQ(2, comp.imag()); run(); EXPECT_DOUBLE_EQ(4, comp.real()); EXPECT_DOUBLE_EQ(3, comp.imag()); } TEST_F(TApp, BuiltinComplexWithDelimiter) { cx comp{1, 2}; app.add_complex("-c,--complex", comp, "", true)->delimiter('+'); args = {"-c", "4+3i"}; std::string help = app.help(); EXPECT_THAT(help, HasSubstr("1")); EXPECT_THAT(help, HasSubstr("2")); EXPECT_THAT(help, HasSubstr("COMPLEX")); EXPECT_DOUBLE_EQ(1, comp.real()); EXPECT_DOUBLE_EQ(2, comp.imag()); run(); EXPECT_DOUBLE_EQ(4, comp.real()); EXPECT_DOUBLE_EQ(3, comp.imag()); args = {"-c", "5+-3i"}; run(); EXPECT_DOUBLE_EQ(5, comp.real()); EXPECT_DOUBLE_EQ(-3, comp.imag()); args = {"-c", "6", "-4i"}; run(); EXPECT_DOUBLE_EQ(6, comp.real()); EXPECT_DOUBLE_EQ(-4, comp.imag()); } TEST_F(TApp, BuiltinComplexIgnoreI) { cx comp{1, 2}; app.add_complex("-c,--complex", comp); args = {"-c", "4", "3i"}; run(); EXPECT_DOUBLE_EQ(4, comp.real()); EXPECT_DOUBLE_EQ(3, comp.imag()); } TEST_F(TApp, BuiltinComplexFail) { cx comp{1, 2}; app.add_complex("-c,--complex", comp); args = {"-c", "4"}; EXPECT_THROW(run(), CLI::ArgumentMismatch); } class spair { public: spair() = default; spair(const std::string &s1, const std::string &s2) : first(s1), second(s2) {} std::string first; std::string second; }; // an example of custom converter that can be used to add new parsing options // On MSVC and possibly some other new compilers this can be a free standing function without the template // specialization but this is compiler dependent namespace CLI { namespace detail { template <> bool lexical_cast(const std::string &input, spair &output) { auto sep = input.find_first_of(':'); if((sep == std::string::npos) && (sep > 0)) { return false; } output = {input.substr(0, sep), input.substr(sep + 1)}; return true; } } // namespace detail } // namespace CLI TEST_F(TApp, custom_string_converter) { spair val; app.add_option("-d,--dual_string", val); args = {"-d", "string1:string2"}; run(); EXPECT_EQ(val.first, "string1"); EXPECT_EQ(val.second, "string2"); } TEST_F(TApp, custom_string_converterFail) { spair val; app.add_option("-d,--dual_string", val); args = {"-d", "string2"}; EXPECT_THROW(run(), CLI::ConversionError); } // an example of custom complex number converter that can be used to add new parsing options #if defined(__has_include) #if __has_include() // an example of custom converter that can be used to add new parsing options #define HAS_REGEX_INCLUDE #endif #endif #ifdef HAS_REGEX_INCLUDE // Gcc 4.8 and older and the corresponding standard libraries have a broken so this would // fail. And if a clang compiler is using libstd++ then this will generate an error as well so this is just a check to // simplify compilation and prevent a much more complicated #if expression #include namespace CLI { namespace detail { // On MSVC and possibly some other new compilers this can be a free standing function without the template // specialization but this is compiler dependent template <> bool lexical_cast>(const std::string &input, std::complex &output) { // regular expression to handle complex numbers of various formats static const std::regex creg( R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)"); std::smatch m; double x = 0.0, y = 0.0; bool worked; std::regex_search(input, m, creg); if(m.size() == 9) { worked = CLI::detail::lexical_cast(m[1], x) && CLI::detail::lexical_cast(m[6], y); if(worked) { if(*m[5].first == '-') { y = -y; } } } else { if((input.back() == 'j') || (input.back() == 'i')) { auto strval = input.substr(0, input.size() - 1); CLI::detail::trim(strval); worked = CLI::detail::lexical_cast(strval, y); } else { std::string ival = input; CLI::detail::trim(ival); worked = CLI::detail::lexical_cast(ival, x); } } if(worked) { output = cx{x, y}; } return worked; } } // namespace detail } // namespace CLI TEST_F(TApp, AddingComplexParserDetail) { bool skip_tests = false; try { // check if the library actually supports regex, it is possible to link against a non working regex in the // standard library std::smatch m; std::string input = "1.5+2.5j"; static const std::regex creg( R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)"); auto rsearch = std::regex_search(input, m, creg); if(!rsearch) { skip_tests = true; } else { EXPECT_EQ(m.size(), 9u); } } catch(...) { skip_tests = true; } if(!skip_tests) { cx comp{0, 0}; app.add_option("-c,--complex", comp, "add a complex number option"); args = {"-c", "1.5+2.5j"}; run(); EXPECT_DOUBLE_EQ(1.5, comp.real()); EXPECT_DOUBLE_EQ(2.5, comp.imag()); args = {"-c", "1.5-2.5j"}; run(); EXPECT_DOUBLE_EQ(1.5, comp.real()); EXPECT_DOUBLE_EQ(-2.5, comp.imag()); } } #endif /// simple class to wrap another with a very specific type constructor and assignment operators to test out some of the /// option assignments template class objWrapper { public: objWrapper() = default; explicit objWrapper(X obj) : val_{obj} {}; objWrapper(const objWrapper &ow) = default; template objWrapper(const TT &obj) = delete; objWrapper &operator=(const objWrapper &) = default; objWrapper &operator=(objWrapper &&) = default; // delete all other assignment operators template void operator=(TT &&obj) = delete; const X &value() const { return val_; } private: X val_; }; // I think there is a bug with the is_assignable in visual studio 2015 it is fixed in later versions // so this test will not compile in that compiler #if !defined(_MSC_VER) || _MSC_VER >= 1910 static_assert(CLI::detail::is_direct_constructible, std::string>::value, "string wrapper isn't properly constructible"); static_assert(!std::is_assignable, std::string>::value, "string wrapper is improperly assignable"); TEST_F(TApp, stringWrapper) { objWrapper sWrapper; app.add_option("-v", sWrapper); args = {"-v", "string test"}; run(); EXPECT_EQ(sWrapper.value(), "string test"); } static_assert(CLI::detail::is_direct_constructible, double>::value, "double wrapper isn't properly assignable"); static_assert(!CLI::detail::is_direct_constructible, int>::value, "double wrapper can be assigned from int"); static_assert(!CLI::detail::is_istreamable>::value, "double wrapper is input streamable and it shouldn't be"); TEST_F(TApp, doubleWrapper) { objWrapper dWrapper; app.add_option("-v", dWrapper); args = {"-v", "2.36"}; run(); EXPECT_EQ(dWrapper.value(), 2.36); args = {"-v", "thing"}; EXPECT_THROW(run(), CLI::ConversionError); } static_assert(CLI::detail::is_direct_constructible, int>::value, "int wrapper is not constructible from int64"); static_assert(!CLI::detail::is_direct_constructible, double>::value, "int wrapper is constructible from double"); static_assert(!CLI::detail::is_istreamable>::value, "int wrapper is input streamable and it shouldn't be"); TEST_F(TApp, intWrapper) { objWrapper iWrapper; app.add_option("-v", iWrapper); args = {"-v", "45"}; run(); EXPECT_EQ(iWrapper.value(), 45); args = {"-v", "thing"}; EXPECT_THROW(run(), CLI::ConversionError); } static_assert(!CLI::detail::is_direct_constructible, int>::value, "float wrapper is constructible from int"); static_assert(!CLI::detail::is_direct_constructible, double>::value, "float wrapper is constructible from double"); static_assert(!CLI::detail::is_istreamable>::value, "float wrapper is input streamable and it shouldn't be"); TEST_F(TApp, floatWrapper) { objWrapper iWrapper; app.add_option, float>("-v", iWrapper); args = {"-v", "45.3"}; run(); EXPECT_EQ(iWrapper.value(), 45.3f); args = {"-v", "thing"}; EXPECT_THROW(run(), CLI::ConversionError); } #endif /// simple class to wrap another with a very specific type constructor to test out some of the option assignments class dobjWrapper { public: dobjWrapper() = default; explicit dobjWrapper(double obj) : dval_{obj} {}; explicit dobjWrapper(int obj) : ival_{obj} {}; double dvalue() const { return dval_; } int ivalue() const { return ival_; } private: double dval_{0.0}; int ival_{0}; }; TEST_F(TApp, dobjWrapper) { dobjWrapper iWrapper; app.add_option("-v", iWrapper); args = {"-v", "45"}; run(); EXPECT_EQ(iWrapper.ivalue(), 45); EXPECT_EQ(iWrapper.dvalue(), 0.0); args = {"-v", "thing"}; EXPECT_THROW(run(), CLI::ConversionError); iWrapper = dobjWrapper{}; args = {"-v", "45.1"}; run(); EXPECT_EQ(iWrapper.ivalue(), 0); EXPECT_EQ(iWrapper.dvalue(), 45.1); } /// simple class to wrap another with a very specific type constructor and assignment operators to test out some of the /// option assignments template class AobjWrapper { public: AobjWrapper() = default; // delete all other constructors template AobjWrapper(TT &&obj) = delete; // single assignment operator void operator=(X val) { val_ = val; } // delete all other assignment operators template void operator=(TT &&obj) = delete; const X &value() const { return val_; } private: X val_; }; static_assert(std::is_assignable &, uint16_t>::value, "AobjWrapper not assignable like it should be "); TEST_F(TApp, uint16Wrapper) { AobjWrapper sWrapper; app.add_option, uint16_t>("-v", sWrapper); args = {"-v", "9"}; run(); EXPECT_EQ(sWrapper.value(), 9u); args = {"-v", "thing"}; EXPECT_THROW(run(), CLI::ConversionError); args = {"-v", "72456245754"}; EXPECT_THROW(run(), CLI::ConversionError); args = {"-v", "-3"}; EXPECT_THROW(run(), CLI::ConversionError); }