diff --git a/src/specmicp_common/string_algorithms.cpp b/src/specmicp_common/string_algorithms.cpp index 3fd1562..7ef9676 100644 --- a/src/specmicp_common/string_algorithms.cpp +++ b/src/specmicp_common/string_algorithms.cpp @@ -1,421 +1,419 @@ /* ============================================================================= Copyright (c) 2014 - 2016 F. Georget Princeton University All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ============================================================================= */ #include "string_algorithms.hpp" #include #define RANGE_SEP ':' namespace specmicp { namespace utils { //! \brief Split a string std::vector split(const std::string& to_split, char separator) { std::vector splitted; auto nb_elem = std::count(to_split.begin(), to_split.end(), separator); splitted.reserve(nb_elem+1); auto start = to_split.begin(); for (auto current=to_split.cbegin(); current!= to_split.cend(); ++current) { if (*current == separator) { splitted.emplace_back(to_split.substr( start - to_split.cbegin(), current-start)); start = current + 1; } } // last value if (start < to_split.cend()) { splitted.emplace_back(to_split.substr(start - to_split.cbegin())); } return splitted; } std::string strip(const std::string& to_trim) { auto start = to_trim.begin(); auto end = to_trim.end(); // at the beginning auto current=to_trim.begin(); for (; current!=to_trim.cend(); ++current) { if (*current != ' ') { start = current; break; } } if (current == to_trim.cend()) {return "";} // empty string // at the end for (auto current=to_trim.cend()-1;current>=to_trim.begin(); --current) { if (*current != ' ') { end = ++current; break; } } return to_trim.substr(start - to_trim.begin(), end-start); } void parse_one_term(std::vector& numbers, const std::string& term); void parse_one_term(std::vector& numbers, const std::string& term); template std::vector range_indices_impl(const std::string& range_str) { std::vector numbers; const uindex_t nb_sep = std::count(range_str.cbegin(), range_str.cend(), ','); numbers.reserve(nb_sep+1); if (nb_sep == 0) { // if only one term parse_one_term(numbers, range_str); } else { const std::vector terms = split(range_str, ','); for (const auto& term: terms) { parse_one_term(numbers, term); } } // sort the indices std::sort(numbers.begin(), numbers.end()); return numbers; } template <> std::vector range_indices(const std::string& range_str) { return range_indices_impl(range_str); } template <> std::vector range_indices(const std::string& range_str) { return range_indices_impl(range_str); } void parse_one_term(std::vector& numbers, const std::string &term) { if (term == "") return; // check for empty terms const auto to_anal = strip(term); const auto is_range = std::find(to_anal.cbegin(), to_anal.cend(), RANGE_SEP); if (is_range == to_anal.cend()) { // if just a number numbers.push_back(std::stol(to_anal)); } else { // if a range const std::string first_str = to_anal.substr(0, is_range-to_anal.cbegin()); const std::string last_str = to_anal.substr(is_range-to_anal.cbegin()+1); const index_t first = std::stol(first_str); const index_t last = std::stol(last_str); for (auto ind=first; ind<=last; ++ind) { numbers.push_back(ind); } } } void parse_one_term(std::vector& numbers, const std::string &term) { if (term == "") return; // check for empty terms const auto to_anal = strip(term); const auto is_range = std::find(to_anal.cbegin(), to_anal.cend(), RANGE_SEP); if (is_range == to_anal.cend()) { // if just a number auto val = std::stol(to_anal); if (val < 0) { throw std::invalid_argument("Negative argurment found in " "positive ranges : '"+std::to_string(val) + "'."); } numbers.push_back(val); } else { // if a range const std::string first_str = to_anal.substr(0, is_range-to_anal.cbegin()); const std::string last_str = to_anal.substr(is_range-to_anal.cbegin()+1); const index_t first = std::stol(first_str); if (first < 0 ) { throw std::invalid_argument("Negative argurment found in " "positive ranges : '"+std::to_string(first) + "'."); } const index_t last = std::stol(last_str); for (auto ind=first; ind<=last; ++ind) { numbers.push_back(ind); } } } template class ExpressionParser { public: ExpressionParser( const std::string& expr, const std::unordered_map& vars ): complete_expr(expr), variables(vars) { } T parse() {return parse_term(complete_expr);} T parse_value(const std::string& value); T parse_factor(const std::string& factor); T parse_term(const std::string& term); private: const std::string& complete_expr; const std::unordered_map& variables; }; template <> scalar_t ExpressionParser::parse_value(const std::string& expr) { auto trimmed_expr = strip(expr); if (trimmed_expr == "") { throw std::invalid_argument("Error while parsing : " + complete_expr); } scalar_t val = std::nan(""); try { std::size_t pos; val = std::stod(trimmed_expr, &pos); if (pos != trimmed_expr.size()) { throw std::logic_error("Error while processing '" + expr + "'. Just a number was expected." "Error occured while processing '" + complete_expr + "'."); } } catch (const std::invalid_argument& e) { auto it = variables.find(trimmed_expr); if (it == variables.cend()) { throw std::out_of_range("Unknown variable '"+ trimmed_expr + "' in parsing of '" + complete_expr +"'."); } val = it->second; } return val; } template <> index_t ExpressionParser::parse_value(const std::string& expr) { auto trimmed_expr = strip(expr); index_t val = INT_MAX; try { std::size_t pos; val = std::stol(trimmed_expr, &pos); if (pos != trimmed_expr.size()) { throw std::logic_error("Error while processing" + expr + ". Just a number was expected." "Error occured while processing " + complete_expr + "."); } } catch (const std::invalid_argument& e) { auto it = variables.find(trimmed_expr); if (it == variables.cend()) { throw std::out_of_range("Unknown variable '"+ trimmed_expr + "' in parsing of '" + complete_expr +"'."); } val = it->second; } return val; } template T ExpressionParser::parse_factor(const std::string& expr) { - auto pred = [](const char& t) -> bool {return ((t == '*') or( t == '/'));}; - auto it = std::find_if(expr.begin(), expr.end(), pred); - if (it == expr.cend()) { + auto it = expr.find_last_of("*/"); + if (it == expr.npos) { return parse_value(expr); } - auto val1 = parse_value(expr.substr(0, it-expr.cbegin())); - auto val2 = parse_factor(expr.substr((it-expr.cbegin())+1)); - if (*it == '*') { + auto val1 = parse_factor(expr.substr(0, it)); + auto val2 = parse_value(expr.substr(it+1)); + if (expr[it] == '*') { return val1*val2; } else { return val1/val2; } return 0; } template T ExpressionParser::parse_term(const std::string& expr) { - auto pred = [](const char& t) -> bool {return ((t == '+') or( t == '-'));}; - auto it = std::find_if(expr.begin(), expr.end(), pred); - if (it == expr.cend()) { + auto it = expr.find_first_of("+-"); + if (it == expr.npos) { return parse_factor(expr); } - else if (it != expr.cbegin()) { - auto val1 = parse_factor(expr.substr(0, it-expr.cbegin())); - auto val2 = parse_factor(expr.substr((it-expr.cbegin())+1)); - if (*it == '+') { + else if (it != 0) { + auto val1 = parse_factor(expr.substr(0, it)); + auto val2 = parse_factor(expr.substr(it+1)); + if (expr[it] == '+') { return val1+val2; } else { return val1-val2; } } else { - if (*it == '-') { + if (expr[it] == '-') { return -parse_term(expr.substr(1)); } else { return parse_term(expr.substr(1)); } } return 0; } template <> scalar_t ExpressionParser::parse_term(const std::string& expr) { auto pred = [](const char& t) -> bool {return ((t == '+') or( t == '-'));}; std::size_t pos = 0; char op; auto it = expr.begin(); while (pos < expr.size()) { it = std::find_if(it, expr.end(), pred); if (it == expr.cend()) { pos = expr.size(); break; } op = *it; pos = it-expr.cbegin(); // xe-y notation => it's a number ! if (op == '-' and expr[pos-1] == 'e' and std::isdigit(expr[pos-2])) { ++it; // jump to next char continue; } else { break; } } if (pos == expr.size()) { return parse_factor(expr); } if (pos > 0) { auto val1 = parse_factor(expr.substr(0, pos)); auto val2 = parse_factor(expr.substr(pos+1)); if (op == '+') { return val1+val2; } else { return val1-val2; } } else { if (op == '-') { return -parse_term(expr.substr(1)); } else { return parse_term(expr.substr(1)); } } return 0; } template <> scalar_t parse_expression( const std::string& expr, const std::unordered_map& variables ) { return ExpressionParser(expr, variables).parse(); } template <> index_t parse_expression( const std::string& expr, const std::unordered_map& variables ) { return ExpressionParser(expr, variables).parse(); } bool string_to_bool(const std::string& to_bool_str) { // Contain the strings that matches true or false static const char* true_str_test[] = {"True", "true", "1", "yes", "Yes", "Y"}; static const char* false_str_test[] = {"False", "false", "0", "no", "No", "N"}; auto trimmed = strip(to_bool_str); bool val = false; // test for true for (auto test: true_str_test) { if (test == trimmed) { val = true; goto exit; } } // test for false for (auto test: false_str_test) { if (test == trimmed) { val = false; goto exit; } } // no match => error throw std::invalid_argument("Unrecognized value when converting to bool '" + trimmed + "'. Recognized value : true/false." ); exit: return val; } } //end namespace utils } //end namespace specmicp diff --git a/tests/specmicp_common/misc.cpp b/tests/specmicp_common/misc.cpp index cd33ddd..46cc636 100644 --- a/tests/specmicp_common/misc.cpp +++ b/tests/specmicp_common/misc.cpp @@ -1,619 +1,620 @@ #include "catch.hpp" #include "specmicp_common/log.hpp" #include "specmicp_common/timer.hpp" #include "specmicp_common/physics/maths.hpp" #include "specmicp_common/moving_average.hpp" #include "specmicp_common/perfs_handler.hpp" #include "specmicp_common/options_handler.hpp" #include "specmicp_common/scope_guard.hpp" #include "specmicp_common/pimpl_ptr.hpp" #include "specmicp_common/cached_vector.hpp" #include "specmicp_common/string_algorithms.hpp" #include "specmicp_common/filesystem.hpp" #include using namespace specmicp; // TOC // ======================= // // 1 - Logger // 2 - Timer // 3 - Average // 4 - Moving Average // 5 - Performance handler // 6 - Options handler // 7 - Scope guard // 8 - Pimpl pointer // 9 - Cached Vector // 10 - Named Cached Vector // 11 - String algorithms // 12 - Filesystem // Logger // ======================= TEST_CASE("Logger", "[io],[log]") { SECTION("Non init logging") { init_logger(nullptr, logger::LogLevel::Warning); // compilation test, and runtime test of not failling nullptr SPAM << "Spam"; DEBUG << "Debug"; INFO << "Info"; WARNING << "Warning"; ERROR << "Error"; CRITICAL << "Critical"; } SECTION("Level test") { std::stringstream stream_log; init_logger(&stream_log, logger::LogLevel::Warning); SPAM << "Spam"; REQUIRE(stream_log.str().size() == 0); DEBUG << "Debug"; REQUIRE(stream_log.str().size() == 0); INFO << "Info"; REQUIRE(stream_log.str().size() == 0); WARNING << "Warning"; REQUIRE(stream_log.str().size() != 0); stream_log.str(""); ERROR << "Error"; REQUIRE(stream_log.str().size() != 0); stream_log.str(""); CRITICAL << "Critical"; REQUIRE(stream_log.str().size() != 0); stream_log.str(""); init_logger(&stream_log, logger::LogLevel::Debug); SPAM << "Spam"; REQUIRE(stream_log.str().size() == 0); DEBUG << "Debug"; #ifdef NDEBUG REQUIRE(stream_log.str().size() == 0); #else REQUIRE(stream_log.str().size() != 0); #endif stream_log.str(""); INFO << "Info"; #ifdef NDEBUG REQUIRE(stream_log.str().size() == 0); #else REQUIRE(stream_log.str().size() != 0); #endif stream_log.str(""); WARNING << "Warning"; REQUIRE(stream_log.str().size() != 0); stream_log.str(""); ERROR << "Error"; REQUIRE(stream_log.str().size() != 0); stream_log.str(""); CRITICAL << "Critical"; REQUIRE(stream_log.str().size() != 0); stream_log.str(""); } } // Timer // ======================= TEST_CASE("Timer", "[time],[CPU]") { SECTION("Timer test") { Timer timer; timer.stop(); CHECK(timer.elapsed_time() >= 0.0); timer.start(); timer.stop(); CHECK(timer.elapsed_time() >= 0.0); CHECK(timer.get_stop() >= timer.get_start()); CHECK(timer.get_ctime_start() != nullptr); CHECK(timer.get_ctime_stop() != nullptr); } } // Average // ==================== #define test_average(method, a, b, sol) \ CHECK(average(a, b) == Approx(sol).epsilon(1e-10)); #define test_average_vector(method, vector, sol) \ CHECK(average(vector) == Approx(sol).epsilon(1e-10)); TEST_CASE("Average", "[average],[maths]") { Vector test_vector(4); test_vector << 1.0, 2.0, 3.0, 4.0; SECTION("Arithmetic average") { test_average(Average::arithmetic, 1.0, 1.0, 1.0); test_average(Average::arithmetic, 2.0, 1.0, 1.5); test_average(Average::arithmetic, -1.0, 1.0, 0.0); test_average_vector(Average::arithmetic, test_vector, 10.0/4); } SECTION("Harmonic average") { test_average(Average::harmonic, 1.0, 1.0, 1.0); test_average(Average::harmonic, 2.0, 1.0, 2.0/(1+0.5)); test_average_vector(Average::harmonic, test_vector, 1.92); } SECTION("Geometric average") { test_average(Average::geometric, 1.0, 1.0, 1.0); test_average(Average::geometric, 1.0, 2.0, std::sqrt(2.0)); test_average_vector(Average::geometric, test_vector, std::pow(24.0, 1.0/4.0)); } } #undef test_average #undef test_average_vector // Moving Average // ======================= TEST_CASE("Moving average", "[average],[timestep]") { SECTION("Moving average test") { utils::ExponentialMovingAverage moving_average(0.1, 1.0); REQUIRE(moving_average.current_value() == 1.0); CHECK(moving_average.add_point(1.0) == 1.0); CHECK(moving_average.add_point(2.0) == 1.1); REQUIRE(moving_average.add_point(3.0) == 0.9*1.1+0.3); moving_average.reset(1.0); REQUIRE(moving_average.current_value() == 1.0); moving_average.set_alpha(0.2); REQUIRE(moving_average.add_point(2.0) == Approx(0.8+0.4)); } } // Performance handler // ======================= struct MockPerf { scalar_t residuals {-1}; index_t nb_iterations {-1}; }; class MockSolverPerf: public PerformanceHandler { public: MockSolverPerf() {} void do_stuff() { get_perfs().residuals = 1e-6; get_perfs().nb_iterations = 10; } void reset_solver() { reset_perfs(); } }; TEST_CASE("PerformanceHandler", "[performance],[base]") { SECTION("Performance handler test") { auto my_solver = MockSolverPerf(); const auto& perfs = my_solver.get_perfs(); CHECK(perfs.nb_iterations == -1); CHECK(perfs.residuals == -1); my_solver.do_stuff(); CHECK(perfs.nb_iterations == 10); CHECK(perfs.residuals == 1e-6); my_solver.reset_solver(); CHECK(perfs.nb_iterations == -1); CHECK(perfs.residuals == -1); } } // Options handler // ======================= struct MockOptions { MockOptions() {} MockOptions(scalar_t res, index_t max_iter): residuals(res), max_iterations(max_iter) {} scalar_t residuals {1e-6}; index_t max_iterations {100}; }; class MockSolverOptions: public OptionsHandler { public: MockSolverOptions() {} MockSolverOptions(scalar_t res, index_t max_iter): OptionsHandler(res, max_iter) {} }; TEST_CASE("Options handler", "[options],[base]") { SECTION("Options handler test") { auto my_solver_default = MockSolverOptions(); const auto& ro_options = my_solver_default.get_options(); CHECK(ro_options.max_iterations == 100); CHECK(ro_options.residuals == 1e-6); auto& rw_options = my_solver_default.get_options(); rw_options.max_iterations = 10; rw_options.residuals = 1e-4; CHECK(ro_options.max_iterations == 10); CHECK(ro_options.residuals == 1e-4); } SECTION("Options handler - non default initialization") { auto my_solver = MockSolverOptions(1e-4, 10); const auto& ro_options = my_solver.get_options(); CHECK(ro_options.max_iterations == 10); CHECK(ro_options.residuals == 1e-4); } } // ScopeGuard // ========== static void will_fail(bool& hop) { auto guard = utils::make_scope_guard([&hop]{hop=true;}); throw std::runtime_error("fail on purpose"); guard.release(); } static void dont_fail(bool& hop) { auto guard = utils::make_scope_guard([&hop]{hop=true;}); hop = false; guard.release(); } TEST_CASE("Scope guard", "[utils]") { SECTION("Catch error") { bool hop = false; REQUIRE_THROWS_AS(will_fail(hop), std::runtime_error); CHECK(hop == true); dont_fail(hop); CHECK(hop == false); } } // pimpl_ptr // ========= // simple Interface + Implementation to test pimpl_ptr class MockImplementation { public: MockImplementation(double a): m_a(a) {} double add(double b) const {return m_a + b;} // Compilation should failed if this one is activated //double add(double b) {return m_a + b;} void set(double new_a) { m_a = new_a;} private: double m_a; }; class MockInterface { public: MockInterface(double a): m_impl(utils::make_pimpl(a)) {} double add(double b) const {return m_impl->add(b);} void set(double new_a) {return m_impl->set(new_a);} private: utils::pimpl_ptr m_impl; }; TEST_CASE("pimpl_ptr", "[utils],[pointer],[const]") { SECTION("Test mock") { auto hop = MockImplementation(1.0); CHECK(hop.add(2.0) == Approx(3.0)); hop.set(2.0); CHECK(hop.add(2.0) == Approx(4.0)); } //SECTION("compilation fail") { // const MockInterface fail1(2.0); // fail1.set(3.0); //} } // Cached Vector // TEST_CASE("Cached vector", "[utils],[container],[cache]") { SECTION("Default") { utils::CachedVector cached; cached.push_back("value0"); cached.push_back(0); CHECK(cached.size() == 2); CHECK(cached.size_cache() == 1); CHECK(cached[0] == "value0"); CHECK(cached[1] == "value0"); for (auto& it: cached) { CHECK(it == "value0"); } cached.emplace_back("truc"); CHECK(cached[2] == "truc"); cached.emplace_back("other"); CHECK(cached[3] == "other"); cached.push_back(2); CHECK(cached.at(4) == "other"); CHECK(cached.size() == 5); CHECK(cached.size_cache() == 3); auto& new_val = cached.fork(1); new_val = "plop"; CHECK(cached[1] == "plop"); for (auto it=cached.begin_index(); it!=cached.cend_index();++it) { CHECK(*it < cached.size()); CHECK(*it >= 0); } //! it's possible, absolutely not a good idea for (auto& value: cached) { value = "reinit"; } CHECK(cached[2] == "reinit"); } SECTION("Default constructor") { static_assert(std::is_default_constructible::value, "String is default constructible" ); utils::CachedVector cached(5); CHECK(cached[4] == ""); } } // Named Cached Vector TEST_CASE("NamedCachedVector", "[utils],[container],[cache]") { SECTION("Default") { utils::NameCachedVector cached( 5, "default_name", "default_value"); CHECK(cached.size_cache() == 1); CHECK(cached.size() == 5); CHECK(cached.get("default_name") == "default_value"); auto& forked = cached.fork(2, "new name"); forked = "new value"; CHECK(cached.size_cache() == 2); CHECK(cached.size() == 5); CHECK(cached[2] == "new value"); CHECK(cached.get("new name") == "new value"); CHECK(cached.has_value("new name")); CHECK(not cached.has_value("not a name")); cached.push_back_cache("another name", "another value"); CHECK(cached.get("another name") == "another value"); CHECK(cached.size_cache() == 3); CHECK(cached.size() == 5); for (auto& value: cached) { CHECK(value != "another value"); } } SECTION("Default constructor") { static_assert(std::is_default_constructible::value, "String is default constructible" ); utils::NameCachedVector cached(5, "default"); CHECK(cached[4] == ""); } } // String Algorithms // ================= TEST_CASE("String algorithms", "[utils],[string],[algorithms]") { SECTION("split") { std::string test1 = "a"; CHECK(utils::split(test1, ',') == std::vector({"a", })); std::string test2 = "a,b,c"; CHECK(utils::split(test2, ',') == std::vector({"a", "b", "c"})); std::string test3 = "a,b,c,"; CHECK(utils::split(test3, ',') == std::vector({"a", "b", "c"})); } SECTION("strip") { CHECK(utils::strip("hop") == "hop"); CHECK(utils::strip(" hop") == "hop"); CHECK(utils::strip("hop ") == "hop"); CHECK(utils::strip(" hop ") == "hop"); CHECK(utils::strip(" hop hop ") == "hop hop"); CHECK(utils::strip(" ") == ""); } SECTION("range_indices") { CHECK(utils::range_indices("1") == std::vector({1, })); CHECK(utils::range_indices("-1:3") == std::vector({-1, 0, 1, 2, 3})); CHECK(utils::range_indices("1,5") == std::vector({1, 5})); CHECK(utils::range_indices("1,3:5") == std::vector({1, 3, 4, 5})); CHECK(utils::range_indices("1,5,") == std::vector({1, 5})); CHECK(utils::range_indices("1, 3:5, 2") == std::vector({1, 2, 3, 4, 5,})); CHECK(utils::range_indices("1,3:5") == std::vector({1, 3, 4, 5})); CHECK_THROWS_AS(utils::range_indices("1,-3:5"), std::invalid_argument); CHECK_THROWS_AS(utils::range_indices("-2"), std::invalid_argument); } SECTION("parse expr") { - CHECK(utils::parse_expression("2.0") == 2.0); - CHECK(utils::parse_expression("-2.0") == -2.0); - CHECK(utils::parse_expression("2.0 +2.0") == 4.0); - CHECK(utils::parse_expression("2.0/ 2.0") == 1.0); - CHECK(utils::parse_expression("3 + 2.0 / 2.0") == 4.0); - CHECK(utils::parse_expression("1e-6") == 1e-6); - CHECK(utils::parse_expression("2*1e-6") == 2e-6); - CHECK(utils::parse_expression("1e-6-2") == 1e-6-2); + CHECK(utils::parse_expression("2.0") == 2.0); + CHECK(utils::parse_expression("-2.0") == -2.0); + CHECK(utils::parse_expression("2.0 +2.0") == 4.0); + CHECK(utils::parse_expression("2.0/ 2.0") == 1.0); + CHECK(utils::parse_expression("3 + 2.0 / 2.0") == 4.0); + CHECK(utils::parse_expression("1e-6") == 1e-6); + CHECK(utils::parse_expression("2*1e-6") == 2e-6); + CHECK(utils::parse_expression("1e-6-2") == 1e-6-2); + CHECK(utils::parse_expression("1e-6/1e-3*1e-2") == 1e-5); CHECK(utils::parse_expression("3 + a / 2.0", {{"a", 2}}) == 4.0); CHECK(utils::parse_expression("a + b / c", {{"a", 2,}, {"b", 3.0}, {"c", 6.0}}) == 2.5); CHECK_THROWS(utils::parse_expression("234.53as")); CHECK_THROWS(utils::parse_expression("3 + a / 2.0", {{"b", 2}})); CHECK_THROWS(utils::parse_expression("3 + a/ / 2.0", {{"a", 2}})); CHECK_THROWS(utils::parse_expression("3 + a // 2.0", {{"a", 2}})); } SECTION("string to bool") { CHECK(utils::string_to_bool("True") == true); CHECK(utils::string_to_bool(" true") == true); CHECK(utils::string_to_bool("1") == true); CHECK(utils::string_to_bool("yes ") == true); CHECK(utils::string_to_bool("Yes") == true); CHECK(utils::string_to_bool(" Y ") == true); CHECK(utils::string_to_bool("False") == false); CHECK(utils::string_to_bool("false ") == false); CHECK(utils::string_to_bool(" 0 ") == false); CHECK(utils::string_to_bool(" no ") == false); CHECK(utils::string_to_bool(" No") == false); CHECK(utils::string_to_bool("N ") == false); CHECK_THROWS_AS(utils::string_to_bool("plop"), std::invalid_argument); CHECK_THROWS_AS(utils::string_to_bool(" plop"), std::invalid_argument); CHECK_THROWS_AS(utils::string_to_bool("Faolse"), std::invalid_argument); } } // Filesystem // =========== TEST_CASE("filesystem", "[io],[dir],[filesystem]") { SECTION("is directory") { CHECK(utils::is_directory(CURRENT_DIRECTORY)); CHECK_FALSE(utils::is_directory("test_common")); CHECK_FALSE(utils::is_directory("mouahahahah")); } SECTION("is directory") { CHECK_FALSE(utils::is_file(CURRENT_DIRECTORY)); CHECK(utils::is_file("test_common")); CHECK(utils::is_file(utils::complete_path( CURRENT_DIRECTORY, "test_common"))); } SECTION("Current directory") { CHECK(utils::get_current_directory() == CURRENT_DIRECTORY); } SECTION("Complete path") { CHECK(utils::complete_path("/usr/include/", "dirent.h") == "/usr/include/dirent.h"); CHECK(utils::complete_path("/usr/include", "dirent.h") == "/usr/include/dirent.h"); } SECTION("Absolute path") { std::string error = ""; CHECK(utils::relative_to_absolute("./test_common", error) == utils::complete_path(CURRENT_DIRECTORY, "test_common")); CHECK(error == ""); CHECK(utils::relative_to_absolute("../../platypus.duck", error) == ""); CHECK(error != ""); } SECTION("Find path") { CHECK(utils::find_path("dirent.h", {"/usr/include"}) == "/usr/include/dirent.h"); CHECK(utils::find_path("not_found_becuase_.h", {"/usr/include"}) == ""); } SECTION("has_env") { CHECK(utils::has_env("USER")); CHECK_FALSE(utils::has_env("platypus_platypus_platypys_35897037289589")); } SECTION("get_env") { CHECK(utils::get_env("USER") != ""); CHECK(utils::get_env("platypus_platypus_platypys_35897037289589") == ""); } }