diff --git a/src/specmicp_common/filesystem.cpp b/src/specmicp_common/filesystem.cpp index 713c47d..20c379c 100644 --- a/src/specmicp_common/filesystem.cpp +++ b/src/specmicp_common/filesystem.cpp @@ -1,213 +1,228 @@ /* ============================================================================= 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 "filesystem.hpp" #include #include #include #include #include #include #include #include "specmicp_common/config.h" #include "log.hpp" static std::string test_name; namespace specmicp { namespace utils { bool is_directory(const std::string& path) { struct stat info; if (stat(path.c_str(), &info) == -1) { return false; } if (S_ISDIR(info.st_mode)) { return true; } return false; } bool is_file(const std::string& path) { struct stat info; if (stat(path.c_str(), &info) == -1) { return false; } if (S_ISREG(info.st_mode)) { return true; } return false; } std::string get_current_directory() { char buf[PATH_MAX]; char* test = getcwd(buf, PATH_MAX); if (test == NULL) { ERROR << "Unable to obtain current working directory"; throw std::runtime_error("Something is wrong," "unable to obtain the current directory."); } return std::string(buf); } std::string complete_path( const std::string& dir, const std::string& file ) { std::string complete = dir; if (dir.back() != '/') { complete += '/'; } complete += file; return complete; } std::string relative_to_absolute( const std::string& rel_path, std::string& error ) { std::string abs_path = ""; char buf[PATH_MAX]; // call posix function char* res = realpath(rel_path.c_str(), buf); if (res == NULL) { // parse error if (errno == ENOENT) { error = "No such file '" + rel_path +"'."; } else if (errno == EACCES) { error = "Read permission denied while searching for '" + rel_path +"'."; } else if (errno == EIO) { error = "I/O error while searching for '" + rel_path +"'."; } else { error = "Error while accessing '" + rel_path + "'."; } } else { // no error, copy buffer abs_path = buf; } return abs_path; } int name_filter(const struct dirent64* entry) { auto res = strncmp(entry->d_name, test_name.c_str(), 256); if (res == 0) { return 1; } return 0; } std::string find_path( std::string filename, const std::vector &directories ) { // check if filename is a path const auto has_sep = filename.find('/'); if (has_sep != std::string::npos) { // already a path => we convert it to absolute std::string error = ""; auto filepath = relative_to_absolute(filename, error); if (filepath == "") { ERROR << error; return ""; // empty string is signal for error } return filepath; } // if not a path we try to find it std::string complete_path_str = ""; test_name = filename.c_str(); for (auto dir: directories) { bool found = false; struct dirent64** entry_list; auto count = scandir64(dir.c_str(), &entry_list, name_filter, alphasort64); if (count < 0) { ERROR << "Problem while scanning directory : " << dir << "."; throw std::runtime_error("Problem while scanning directory " + dir + "."); } if (count == 0) { continue; } if (count > 1) { WARNING << "More that one match for file '" << filename << "in : " << dir << "."; } for (auto ind=0; indd_name); } free(entry); // need to free everything } free(entry_list); if (found) break; } return complete_path_str; } +bool has_env( + const std::string& env_var + ) +{ +#ifdef SPECMICP_HAVE_SECURE_GETENV + char* env = secure_getenv(env_var.c_str()); +#else + char* env = getenv(env_var.c_str()); +#endif + if (env == NULL) { + return false; + } + return true; +} + std::string get_env( const std::string& env_var ) { #ifdef SPECMICP_HAVE_SECURE_GETENV char* env = secure_getenv(env_var.c_str()); #else char* env = getenv(env_var.c_str()); #endif if (env == NULL) { return ""; } else return env; } } // end namespace utils } // end namespace specmicp diff --git a/src/specmicp_common/filesystem.hpp b/src/specmicp_common/filesystem.hpp index 4172765..8695b3f 100644 --- a/src/specmicp_common/filesystem.hpp +++ b/src/specmicp_common/filesystem.hpp @@ -1,82 +1,87 @@ /* ============================================================================= 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. * ============================================================================= */ #ifndef SPECMICP_UTILS_FILESYSTEM #define SPECMICP_UTILS_FILESYSTEM #include "macros.hpp" #include #include namespace specmicp { namespace utils { //! \brief Check that the directory exist bool SPECMICP_DLL_PUBLIC is_directory(const std::string& path); //! \brief Check that the directory exist bool SPECMICP_DLL_PUBLIC is_file(const std::string& path); //! \brief Return the current directory std::string SPECMICP_DLL_PUBLIC get_current_directory(); //! \brief Complete a path std::string SPECMICP_DLL_PUBLIC complete_path( const std::string& dir, const std::string& file ); //! \brief Return an absolute path from a relative one //! //! If an error is detected then the return string will be empty, //! and error will contain a message about the error std::string SPECMICP_DLL_PUBLIC relative_to_absolute( const std::string& rel_path, std::string& error ); //! \brief Return the complete path to a file from a set of directories std::string SPECMICP_DLL_PUBLIC find_path( std::string filename, const std::vector& directories ); +//! \brief Return true if the environment variable is defined +bool SPECMICP_DLL_PUBLIC has_env( + const std::string& env_var + ); + //! \brief Return an environment variable std::string SPECMICP_DLL_PUBLIC get_env( const std::string& env_var ); } // end namespace utils } // end namespace specmicp #endif // SPECMICP_UTILS_FILESYSTEM diff --git a/tests/specmicp_common/misc.cpp b/tests/specmicp_common/misc.cpp index 1bf74b2..cd33ddd 100644 --- a/tests/specmicp_common/misc.cpp +++ b/tests/specmicp_common/misc.cpp @@ -1,615 +1,619 @@ #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("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") == ""); } }