From 5f85bd7d156b141508b7dd7ec0e340e73c4970a5 Mon Sep 17 00:00:00 2001 From: Sam Yates <yates@cscs.ch> Date: Fri, 21 Apr 2017 13:58:15 +0200 Subject: [PATCH] Try more places for validation data; workaround CMake FindCUDA bug. (#233) Fixes #232. * Try `NMC_DATADIR` environment variable for validation data path, or else if the #defined `NMC_DATADIR` does not point to a directory, try `./validation/data` and `../validation/data`. * Don't define `NMC_DATADIR` if CMake version 3.7 or 3.8. * Extend C++17 filesystem emulation with (POSIX) implementation of `is_directory` and supporting classes, functions and enums `filesystem_error`, `file_status`, `status(..)`, `file_type` and `perms`. --- src/util/path.cpp | 88 +++- src/util/path.hpp | 633 +++++++++++++++++---------- tests/unit/CMakeLists.txt | 4 +- tests/unit/test_path.cpp | 185 ++++++-- tests/validation/CMakeLists.txt | 6 +- tests/validation/validate.cpp | 8 +- tests/validation/validation_data.cpp | 33 ++ tests/validation/validation_data.hpp | 21 +- 8 files changed, 685 insertions(+), 293 deletions(-) diff --git a/src/util/path.cpp b/src/util/path.cpp index 53cc710b..88c74b56 100644 --- a/src/util/path.cpp +++ b/src/util/path.cpp @@ -1,25 +1,35 @@ +// POSIX headers +extern "C" { +#define _POSIX_C_SOURCE 2 +#include <glob.h> +#include <sys/stat.h> +} + +// GLOB_TILDE and GLOB_BRACE are non-standard but convenient and common +// flags for glob(). + +#ifndef GLOB_TILDE +#define GLOB_TILDE 0 +#endif +#ifndef GLOB_BRACE +#define GLOB_BRACE 0 +#endif + +#include <cerrno> + #include <util/scope_exit.hpp> #include <util/path.hpp> -// POSIX header -#include <glob.h> - namespace nest { namespace mc { namespace util { +namespace posix { std::vector<path> glob(const std::string& pattern) { std::vector<path> paths; - - int flags = GLOB_MARK | GLOB_NOCHECK; -#if defined(GLOB_TILDE) - flags |= GLOB_TILDE; -#endif -#if defined(GLOB_TILDE) - flags |= GLOB_BRACE;; -#endif - glob_t matches; + + int flags = GLOB_MARK | GLOB_NOCHECK | GLOB_TILDE | GLOB_BRACE; auto r = ::glob(pattern.c_str(), flags, nullptr, &matches); auto glob_guard = on_scope_exit([&]() { ::globfree(&matches); }); @@ -37,6 +47,60 @@ std::vector<path> glob(const std::string& pattern) { return paths; } +namespace impl { + file_status status(const char* p, int r, struct stat& st, std::error_code& ec) { + if (!r) { + // Success: + ec.clear(); + perms p = static_cast<perms>(st.st_mode&07777); + switch (st.st_mode&S_IFMT) { + case S_IFSOCK: + return file_status{file_type::socket, p}; + case S_IFLNK: + return file_status{file_type::symlink, p}; + case S_IFREG: + return file_status{file_type::regular, p}; + case S_IFBLK: + return file_status{file_type::block, p}; + case S_IFDIR: + return file_status{file_type::directory, p}; + case S_IFCHR: + return file_status{file_type::character, p}; + case S_IFIFO: + return file_status{file_type::fifo, p}; + default: + return file_status{file_type::unknown, p}; + } + } + + // Handle error cases especially. + + if ((errno==ENOENT || errno==ENOTDIR) && p && *p) { + // If a non-empty path, return `not_found`. + ec.clear(); + return file_status{file_type::not_found}; + } + else { + ec = std::error_code(errno, std::generic_category()); + return file_status{file_type::none}; + } + } +} // namespace impl + + +file_status status(const path& p, std::error_code& ec) { + struct stat st; + int r = stat(p.c_str(), &st); + return impl::status(p.c_str(), r, st, ec); +} + +file_status symlink_status(const path& p, std::error_code& ec) { + struct stat st; + int r = lstat(p.c_str(), &st); + return impl::status(p.c_str(), r, st, ec); +} + +} // namespace posix } // namespace util } // namespace mc } // namespace nest diff --git a/src/util/path.hpp b/src/util/path.hpp index d89873ad..e3114518 100644 --- a/src/util/path.hpp +++ b/src/util/path.hpp @@ -3,7 +3,7 @@ /* * Small subset of C++17 filesystem::path functionality * - * Missing functionality: + * Missing path functionality: * * Wide character methods * * Locale-aware conversions * * Path decomposition @@ -12,11 +12,13 @@ * * Path element iteration * * If we do want to support non-POSIX paths properly, consider - * making posix::path into a generic `basic_path` parameterized + * making posix_path into a generic `basic_path` parameterized * on `value_type`, `preferred_separator`, and using CRTP to * handle system-specific conversions. */ +#include <cstddef> +#include <exception> #include <string> #include <iostream> #include <utility> @@ -28,251 +30,400 @@ namespace nest { namespace mc { namespace util { -namespace posix { - class path { - public: - using value_type = char; - using string_type = std::basic_string<value_type>; - static constexpr value_type preferred_separator = '/'; - - path() = default; - path(const path&) = default; - path(path&&) = default; - - path& operator=(const path&) = default; - path& operator=(path&&) = default; - - // Swap internals - - void swap(path& other) { - p_.swap(other.p_); - } - - // Construct or assign from value_type string or sequence. - - template <typename Source> - path(Source&& source) { assign(std::forward<Source>(source)); } - - template <typename Iter> - path(Iter b, Iter e) { assign(b, e); } - - template <typename Source> - path& operator=(const Source& source) { return assign(source); } - - path& assign(const path& other) { - p_ = other.p_; - return *this; - } - - path& assign(const string_type& source) { - p_ = source; - return *this; - } - - path& assign(const value_type* source) { - p_ = source; - return *this; - } - - template <typename Seq, typename = enable_if_sequence_t<Seq>> - path& assign(const Seq& seq) { - util::assign(p_, seq); - return *this; - } - - template <typename Iter> - path& assign(Iter b, Iter e) { - p_.assign(b, e); - return *this; - } - - // Empty? - bool empty() const { return p_.empty(); } - - // Make empty - void clear() { p_.clear(); } - - // Append path components - - template <typename Source> - path& append(const Source& source) { - return append(path(source)); - } - - template <typename Iter> - path& append(Iter b, Iter e) { - return append(path(b, e)); - } - - template <typename Source> - path& operator/=(const Source& source) { - return append(path(source)); - } - - path& append(const path& tail) { - if (!p_.empty() && - !is_separator(p_.back()) && - !tail.p_.empty() && - !is_separator(tail.p_.front())) - { - p_ += preferred_separator; - } - if (!p_.empty() && - !tail.empty() && - is_separator(p_.back()) && - is_separator(tail.p_.front())) - { - p_ += tail.p_.substr(1); - } - else { - p_ += tail.p_; - } - return *this; - } - - // Concat to path - - template <typename Source> - path& concat(const Source& source) { - return concat(path(source)); - } - - template <typename Iter> - path& concat(Iter b, Iter e) { - return concat(path(b, e)); - } - - template <typename Source> - path& operator+=(const Source& source) { - return concat(path(source)); - } - - path& concat(const path& tail) { +class posix_path { +public: + using value_type = char; + using string_type = std::basic_string<value_type>; + static constexpr value_type preferred_separator = '/'; + + posix_path() = default; + posix_path(const posix_path&) = default; + posix_path(posix_path&&) = default; + + posix_path& operator=(const posix_path&) = default; + posix_path& operator=(posix_path&&) = default; + + // Swap internals + + void swap(posix_path& other) { + p_.swap(other.p_); + } + + // Construct or assign from value_type string or sequence. + + template <typename Source> + posix_path(Source&& source) { assign(std::forward<Source>(source)); } + + template <typename Iter> + posix_path(Iter b, Iter e) { assign(b, e); } + + template <typename Source> + posix_path& operator=(const Source& source) { return assign(source); } + + posix_path& assign(const posix_path& other) { + p_ = other.p_; + return *this; + } + + posix_path& assign(const string_type& source) { + p_ = source; + return *this; + } + + posix_path& assign(const value_type* source) { + p_ = source; + return *this; + } + + template <typename Seq, typename = enable_if_sequence_t<Seq>> + posix_path& assign(const Seq& seq) { + util::assign(p_, seq); + return *this; + } + + template <typename Iter> + posix_path& assign(Iter b, Iter e) { + p_.assign(b, e); + return *this; + } + + bool empty() const { return p_.empty(); } + + void clear() { p_.clear(); } + + // Append posix_path components + template <typename Source> + posix_path& append(const Source& source) { + return append(posix_path(source)); + } + + template <typename Iter> + posix_path& append(Iter b, Iter e) { + return append(posix_path(b, e)); + } + + template <typename Source> + posix_path& operator/=(const Source& source) { + return append(posix_path(source)); + } + + posix_path& append(const posix_path& tail) { + if (!p_.empty() && + !is_separator(p_.back()) && + !tail.p_.empty() && + !is_separator(tail.p_.front())) + { + p_ += preferred_separator; + } + if (!p_.empty() && + !tail.empty() && + is_separator(p_.back()) && + is_separator(tail.p_.front())) + { + p_ += tail.p_.substr(1); + } + else { p_ += tail.p_; - return *this; - } - - // Native path string - - const value_type* c_str() const { return p_.c_str(); } - const string_type& native() const { return p_; } - operator string_type() const { return p_; } - - // Generic path string (same for POSIX) - - std::string generic_string() const { return p_; } - - // Queries - - bool is_absolute() const { - return !p_.empty() && p_.front()==preferred_separator; - } - - bool is_relative() const { - return !is_absolute(); - } - - // Compare - - int compare(const string_type& s) const { - return compare(path(s)); - } - - int compare(const value_type* s) const { - return compare(path(s)); - } - - int compare(const path& other) const { - // TODO: replace with cleaner implementation if/when iterators - // are implemented. - - return canonical().compare(other.canonical()); - } - - // Non-member functions - - friend path operator/(const path& a, const path& b) { - path p(a); - return p/=b; - } - - friend std::size_t hash_value(const path& p) { - std::hash<path::string_type> hash; - return hash(p.p_); - } - - friend std::ostream& operator<<(std::ostream& o, const path& p) { - return o << p.native(); - } - - friend std::istream& operator>>(std::istream& i, path& p) { - path::string_type s; - i >> s; - p = s; - return i; } - - friend void swap(path& lhs, path& rhs) { - lhs.swap(rhs); - } - - friend bool operator<(const path& p, const path& q) { - return p.compare(q)<0; - } - - friend bool operator>(const path& p, const path& q) { - return p.compare(q)>0; - } - - friend bool operator==(const path& p, const path& q) { - return p.compare(q)==0; - } - - friend bool operator!=(const path& p, const path& q) { - return p.compare(q)!=0; - } - - friend bool operator<=(const path& p, const path& q) { - return p.compare(q)<=0; - } - - friend bool operator>=(const path& p, const path& q) { - return p.compare(q)>=0; - } - - protected: - static bool is_separator(value_type c) { - return c=='/' || c==preferred_separator; - } - - std::string canonical() const { - std::string n; - value_type prev = 0; - for (value_type c: p_) { - if (is_separator(c)) { - if (!is_separator(prev)) { - n += '/'; - } - } - else { - n += c; + return *this; + } + + // Concat to posix_path + template <typename Source> + posix_path& concat(const Source& source) { + return concat(posix_path(source)); + } + + template <typename Iter> + posix_path& concat(Iter b, Iter e) { + return concat(posix_path(b, e)); + } + + template <typename Source> + posix_path& operator+=(const Source& source) { + return concat(posix_path(source)); + } + + posix_path& concat(const posix_path& tail) { + p_ += tail.p_; + return *this; + } + + // Native posix_path string + const value_type* c_str() const { return p_.c_str(); } + const string_type& native() const { return p_; } + operator string_type() const { return p_; } + + // Generic posix_path string (same for POSIX) + std::string generic_string() const { return p_; } + + // Queries + bool is_absolute() const { + return !p_.empty() && p_.front()==preferred_separator; + } + + bool is_relative() const { + return !is_absolute(); + } + + int compare(const string_type& s) const { + return compare(posix_path(s)); + } + + int compare(const value_type* s) const { + return compare(posix_path(s)); + } + + int compare(const posix_path& other) const { + // TODO: replace with cleaner implementation if/when iterators + // are implemented. + + return canonical().compare(other.canonical()); + } + + // Non-member functions + + friend posix_path operator/(const posix_path& a, const posix_path& b) { + posix_path p(a); + return p/=b; + } + + friend std::size_t hash_value(const posix_path& p) { + std::hash<posix_path::string_type> hash; + return hash(p.p_); + } + + friend std::ostream& operator<<(std::ostream& o, const posix_path& p) { + return o << p.native(); + } + + friend std::istream& operator>>(std::istream& i, posix_path& p) { + posix_path::string_type s; + i >> s; + p = s; + return i; + } + + friend void swap(posix_path& lhs, posix_path& rhs) { + lhs.swap(rhs); + } + + friend bool operator<(const posix_path& p, const posix_path& q) { + return p.compare(q)<0; + } + + friend bool operator>(const posix_path& p, const posix_path& q) { + return p.compare(q)>0; + } + + friend bool operator==(const posix_path& p, const posix_path& q) { + return p.compare(q)==0; + } + + friend bool operator!=(const posix_path& p, const posix_path& q) { + return p.compare(q)!=0; + } + + friend bool operator<=(const posix_path& p, const posix_path& q) { + return p.compare(q)<=0; + } + + friend bool operator>=(const posix_path& p, const posix_path& q) { + return p.compare(q)>=0; + } + +protected: + static bool is_separator(value_type c) { + return c=='/' || c==preferred_separator; + } + + std::string canonical() const { + std::string n; + value_type prev = 0; + for (value_type c: p_) { + if (is_separator(c)) { + if (!is_separator(prev)) { + n += '/'; } - prev = c; } - if (!n.empty() && n.back()=='/') { - n += '.'; + else { + n += c; } - return n; - } - - string_type p_; - }; -} // namespace posix + prev = c; + } + if (!n.empty() && n.back()=='/') { + n += '.'; + } + return n; + } + + string_type p_; +}; + +// Paths for now are just POSIX paths. +using path = posix_path; + +// Following C++17 std::filesystem::perms: +enum class perms: unsigned { + none = 0, + owner_read = 0400, + owner_write = 0200, + owner_exec = 0100, + owner_all = 0700, + group_read = 040, + group_write = 020, + group_exec = 010, + group_all = 070, + others_read = 04, + others_write = 02, + others_exec = 01, + others_all = 07, + all = 0777, + set_uid = 04000, + set_gid = 02000, + sticky_bit = 01000, + mask = 07777, + unknown = 0xffff, + add_perms = 0x10000, + remove_perms = 0x20000, + resolve_symlinks = 0x40000, +}; + +inline perms operator&(perms x, perms y) { return static_cast<perms>(static_cast<unsigned>(x)&static_cast<unsigned>(y)); } +inline perms operator|(perms x, perms y) { return static_cast<perms>(static_cast<unsigned>(x)|static_cast<unsigned>(y)); } +inline perms operator^(perms x, perms y) { return static_cast<perms>(static_cast<unsigned>(x)^static_cast<unsigned>(y)); } +inline perms operator~(perms x) { return static_cast<perms>(~static_cast<unsigned>(x)); } +inline perms& operator&=(perms& x, perms y) { return x = x&y; } +inline perms& operator|=(perms& x, perms y) { return x = x|y; } +inline perms& operator^=(perms& x, perms y) { return x = x^y; } + +enum class file_type { + none, not_found, regular, directory, symlink, block, + character, fifo, socket, unknown +}; + +class file_status { +public: + explicit file_status(file_type type, perms permissions = perms::unknown): + type_(type), perm_(permissions) + {} + + file_status(): file_status(file_type::none) {} + + file_status(const file_status&) = default; + file_status(file_status&&) = default; + file_status& operator=(const file_status&) = default; + file_status& operator=(file_status&&) = default; + + file_type type() const { return type_; } + void type(file_type type) { type_ = type; } + + perms permissions() const { return perm_; } + void permissions(perms perm) { perm_ = perm; } + +private: + file_type type_; + perms perm_; +}; + +class filesystem_error: public std::system_error { +public: + filesystem_error(const std::string& what_arg, std::error_code ec): + std::system_error(ec, what_arg) {} + + filesystem_error(const std::string& what_arg, const path& p1, std::error_code ec): + std::system_error(ec, what_arg), p1_(p1) {} + + filesystem_error(const std::string& what_arg, const path& p1, const path& p2, std::error_code ec): + std::system_error(ec, what_arg), p1_(p1), p2_(p2) {} + + const path& path1() const { return p1_; } + const path& path2() const { return p2_; } + +private: + path p1_, p2_; +}; + +// POSIX implementations of path queries (see path.cpp for implementations). -using path = posix::path; - -// POSIX glob (3) wrapper. -std::vector<path> glob(const std::string& pattern); +namespace posix { + file_status status(const path&, std::error_code&); + file_status symlink_status(const path&, std::error_code&); + + // POSIX glob (3) wrapper (not part of std::filesystem!). + std::vector<path> glob(const std::string& pattern); +} + +inline file_status status(const path& p, std::error_code& ec) { + return posix::status(p, ec); +} + +inline file_status symlink_status(const path& p, std::error_code& ec) { + return posix::symlink_status(p, ec); +} + +inline std::vector<path> glob(const std::string& pattern) { + return posix::glob(pattern); +} + +// Wrappers for `status()`, again following std::filesystem. + +inline file_status status(const path& p) { + std::error_code ec; + auto r = ::nest::mc::util::posix::status(p, ec); + if (ec) { + throw filesystem_error("status()", p, ec); + } + return r; +} + +inline bool is_directory(file_status s) { + return s.type()==file_type::directory; +} + +inline bool is_directory(const path& p) { + return is_directory(status(p)); +} + +inline bool is_directory(const path& p, std::error_code& ec) { + return is_directory(status(p, ec)); +} + +inline bool is_regular_file(file_status s) { + return s.type()==file_type::regular; +} + +inline bool is_regular_file(const path& p) { + return is_regular_file(status(p)); +} + +inline bool is_regular_file(const path& p, std::error_code& ec) { + return is_regular_file(status(p, ec)); +} + +inline bool is_character_file(file_status s) { + return s.type()==file_type::character; +} + +inline bool is_character_file(const path& p) { + return is_character_file(status(p)); +} + +inline bool is_character_file(const path& p, std::error_code& ec) { + return is_character_file(status(p, ec)); +} + +inline bool exists(file_status s) { + return s.type()!=file_type::not_found; +} + +inline bool exists(const path& p) { + return exists(status(p)); +} + +inline bool exists(const path& p, std::error_code& ec) { + return exists(status(p, ec)); +} } // namespace util } // namespace mc diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 2aa34ffb..3ac4c1ff 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -72,11 +72,11 @@ set(TEST_SOURCES test.cpp ) -add_definitions("-DDATADIR=\"${PROJECT_SOURCE_DIR}/data\"") set(TARGETS test.exe) add_executable(test.exe ${TEST_SOURCES} ${HEADERS}) +target_compile_definitions(test.exe PUBLIC "-DDATADIR=\"${PROJECT_SOURCE_DIR}/data\"") if (NMC_AUTO_RUN_MODCC_ON_CHANGES) add_dependencies(test.exe build_test_mods) @@ -85,6 +85,8 @@ endif() target_include_directories(test.exe PRIVATE "${mech_proto_dir}/..") if(NMC_WITH_CUDA) + # Omit -DDATADIR for cuda target because of CMake 3.7/3.8 FindCUDA quoting bug. + set(TARGETS ${TARGETS} test_cuda.exe) cuda_add_executable(test_cuda.exe ${TEST_CUDA_SOURCES} ${HEADERS}) target_link_libraries(test_cuda.exe LINK_PUBLIC gpu) diff --git a/tests/unit/test_path.cpp b/tests/unit/test_path.cpp index 54530f73..e48e4d20 100644 --- a/tests/unit/test_path.cpp +++ b/tests/unit/test_path.cpp @@ -11,7 +11,7 @@ using namespace nest::mc::util; TEST(path, posix_ctor) { // test constructor ans assignment overloads with sample character sequences. - posix::path p1; + posix_path p1; EXPECT_EQ("", p1.native()); EXPECT_TRUE(p1.empty()); @@ -19,10 +19,10 @@ TEST(path, posix_ctor) { std::string str_cs(cs); std::vector<char> vec_cs(str_cs.begin(), str_cs.end()); - posix::path p2(cs); - posix::path p3(str_cs); - posix::path p4(str_cs.begin(), str_cs.end()); - posix::path p5(vec_cs.begin(), vec_cs.end()); + posix_path p2(cs); + posix_path p3(str_cs); + posix_path p4(str_cs.begin(), str_cs.end()); + posix_path p5(vec_cs.begin(), vec_cs.end()); EXPECT_FALSE(p2.empty()); EXPECT_EQ(str_cs, p2.native()); @@ -30,14 +30,14 @@ TEST(path, posix_ctor) { EXPECT_EQ(str_cs, p4.native()); EXPECT_EQ(str_cs, p5.native()); - posix::path p6(p2); + posix_path p6(p2); EXPECT_EQ(str_cs, p6.native()); - posix::path p7(std::move(p6)); + posix_path p7(std::move(p6)); EXPECT_EQ(str_cs, p7.native()); // test operator= overloads (and ref return values) - posix::path p; + posix_path p; EXPECT_EQ(str_cs, (p=p2).native()); EXPECT_EQ(str_cs, (p=cs).native()); EXPECT_EQ(str_cs, (p=str_cs).native()); @@ -57,18 +57,18 @@ TEST(path, posix_native) { std::string ps = "/abs/path"; std::string qs = "rel/path.ext"; - EXPECT_EQ(ps, posix::path{ps}.native()); - EXPECT_EQ(qs, posix::path{qs}.native()); + EXPECT_EQ(ps, posix_path{ps}.native()); + EXPECT_EQ(qs, posix_path{qs}.native()); // default string conversion - std::string ps_bis = posix::path{ps}; - std::string qs_bis = posix::path{qs}; + std::string ps_bis = posix_path{ps}; + std::string qs_bis = posix_path{qs}; EXPECT_EQ(ps, ps_bis); EXPECT_EQ(qs, qs_bis); // cstr - const char *c = posix::path{ps}.c_str(); + const char *c = posix_path{ps}.c_str(); EXPECT_TRUE(!std::strcmp(c, ps.c_str())); } @@ -81,9 +81,9 @@ TEST(path, posix_generic) { } TEST(path, posix_append) { - posix::path p1{""}, q1{"rel"}; + posix_path p1{""}, q1{"rel"}; - posix::path p(p1); + posix_path p(p1); p.append(q1); EXPECT_EQ(p1/q1, p); @@ -92,7 +92,7 @@ TEST(path, posix_append) { EXPECT_EQ(p1/q1, p); EXPECT_EQ("rel", p.native()); - posix::path p2{"ab"}, q2{"rel"}; + posix_path p2{"ab"}, q2{"rel"}; p = p2; p.append(q2); @@ -103,14 +103,14 @@ TEST(path, posix_append) { EXPECT_EQ(p2/q2, p); EXPECT_EQ("ab/rel", p.native()); - EXPECT_EQ("foo/bar", (posix::path("foo/")/posix::path("/bar")).native()); - EXPECT_EQ("foo/bar", (posix::path("foo")/posix::path("/bar")).native()); - EXPECT_EQ("foo/bar", (posix::path("foo/")/posix::path("bar")).native()); - EXPECT_EQ("/foo/bar/", (posix::path("/foo/")/posix::path("/bar/")).native()); + EXPECT_EQ("foo/bar", (posix_path("foo/")/posix_path("/bar")).native()); + EXPECT_EQ("foo/bar", (posix_path("foo")/posix_path("/bar")).native()); + EXPECT_EQ("foo/bar", (posix_path("foo/")/posix_path("bar")).native()); + EXPECT_EQ("/foo/bar/", (posix_path("/foo/")/posix_path("/bar/")).native()); } TEST(path, compare) { - posix::path p1("/a/b"), p2("/a//b"), p3("/a/b/c/."), p4("/a/b/c/"), p5("a/bb/c"), p6("a/b/c/"); + posix_path p1("/a/b"), p2("/a//b"), p3("/a/b/c/."), p4("/a/b/c/"), p5("a/bb/c"), p6("a/b/c/"); EXPECT_EQ(p1, p2); EXPECT_LE(p1, p2); @@ -135,9 +135,9 @@ TEST(path, compare) { } TEST(path, posix_concat) { - posix::path p1{""}, q1{"tail"}; + posix_path p1{""}, q1{"tail"}; - posix::path p(p1); + posix_path p(p1); p.concat(q1); EXPECT_EQ("tail", p.native()); @@ -145,7 +145,7 @@ TEST(path, posix_concat) { p += q1; EXPECT_EQ("tail", p.native()); - posix::path p2{"ab"}, q2{"cd"}; + posix_path p2{"ab"}, q2{"cd"}; p = p2; p.concat(q2); @@ -157,29 +157,29 @@ TEST(path, posix_concat) { } TEST(path, posix_absrel_query) { - posix::path p1("/abc/def"); + posix_path p1("/abc/def"); EXPECT_FALSE(p1.is_relative()); EXPECT_TRUE(p1.is_absolute()); - posix::path p2("abc/def"); + posix_path p2("abc/def"); EXPECT_TRUE(p2.is_relative()); EXPECT_FALSE(p2.is_absolute()); - posix::path p3(""); + posix_path p3(""); EXPECT_TRUE(p3.is_relative()); EXPECT_FALSE(p3.is_absolute()); - posix::path p4("/"); + posix_path p4("/"); EXPECT_FALSE(p4.is_relative()); EXPECT_TRUE(p4.is_absolute()); - posix::path p5(".."); + posix_path p5(".."); EXPECT_TRUE(p3.is_relative()); EXPECT_FALSE(p3.is_absolute()); } TEST(path, posix_swap) { - posix::path p1("foo"), p2("/bar"); + posix_path p1("foo"), p2("/bar"); p1.swap(p2); EXPECT_EQ("foo", p2.native()); @@ -193,7 +193,7 @@ TEST(path, posix_swap) { TEST(path, posix_iostream) { std::istringstream ss("/quux/xyzzy"); - posix::path p; + posix_path p; ss >> p; EXPECT_EQ("/quux/xyzzy", p.native()); @@ -202,3 +202,126 @@ TEST(path, posix_iostream) { EXPECT_EQ("/quux/xyzzy", uu.str()); } +TEST(path, filesystem_error) { + auto io_error = std::make_error_code(std::errc::io_error); + + filesystem_error err0("err0", io_error); + filesystem_error err1("err1", "/one", io_error); + filesystem_error err2("err2", "/one", "/two", io_error); + + EXPECT_TRUE(dynamic_cast<std::system_error*>(&err0)); + EXPECT_NE(std::string::npos, std::string(err0.what()).find("err0")); + EXPECT_EQ(path{}, err0.path1()); + EXPECT_EQ(path{}, err0.path2()); + EXPECT_EQ(io_error, err0.code()); + + EXPECT_NE(std::string::npos, std::string(err1.what()).find("err1")); + EXPECT_EQ(path("/one"), err1.path1()); + EXPECT_EQ(path{}, err1.path2()); + EXPECT_EQ(io_error, err1.code()); + + EXPECT_NE(std::string::npos, std::string(err2.what()).find("err2")); + EXPECT_EQ(path("/one"), err2.path1()); + EXPECT_EQ(path("/two"), err2.path2()); + EXPECT_EQ(io_error, err2.code()); +} + +TEST(path, posix_status) { + // Expect (POSIX) filesystem to have: + // / directory + // . directory + // /dev/null character special file + + try { + file_status root = status("/"); + file_status dot = status("."); + file_status dev_null = status("/dev/null"); + // file and /none/ should not exist, but we don't expect an error + file_status nonesuch = status("/none/such"); + + EXPECT_EQ(file_type::directory, root.type()); + EXPECT_EQ(file_type::directory, dot.type()); + EXPECT_EQ(file_type::character, dev_null.type()); + EXPECT_EQ(file_type::not_found, nonesuch.type()); + } + catch (filesystem_error& e) { + FAIL() << "unexpected error with status(): " << e.what(); + } + + try { + file_status empty = status(""); + (void)empty; + // should throw error wrapping ENOENT + FAIL() << "status() did not throw on empty string"; + } + catch (filesystem_error& e) { + EXPECT_TRUE(e.code().default_error_condition()==std::errc::no_such_file_or_directory); + } + + try { + std::string way_too_long(1<<20,'z'); + file_status oops = status(way_too_long); + (void)oops; + // should throw error wrapping ENAMETOOLONG + FAIL() << "status() did not throw on stupendously long file name"; + } + catch (filesystem_error& e) { + EXPECT_EQ(e.code().default_error_condition(), std::errc::filename_too_long); + } + try { + std::error_code ec; + file_status empty = status("", ec); + EXPECT_EQ(file_type::none, empty.type()); + EXPECT_EQ(ec.default_error_condition(), std::errc::no_such_file_or_directory); + } + catch (filesystem_error& e) { + FAIL() << "status(path, ec) should not throw"; + } +} + +TEST(path, is_wrappers) { + EXPECT_TRUE(exists("/")); + EXPECT_TRUE(is_directory("/")); + EXPECT_FALSE(is_regular_file("/")); + + EXPECT_TRUE(exists("/dev/null")); + EXPECT_TRUE(is_character_file("/dev/null")); + EXPECT_FALSE(is_regular_file("/")); + + EXPECT_FALSE(exists("/none/such")); + EXPECT_FALSE(is_regular_file("/")); + EXPECT_TRUE(is_directory("/")); + + EXPECT_THROW(exists(""), filesystem_error); +} + +TEST(path, permissions) { + perms p = perms::owner_read | perms::owner_write | perms::owner_exec; + EXPECT_EQ(perms::owner_all, p); + EXPECT_EQ(perms::none, p & perms::group_all); + EXPECT_EQ(perms::none, p & perms::others_all); + + p = perms::group_read | perms::group_write | perms::group_exec; + EXPECT_EQ(perms::group_all, p); + EXPECT_EQ(perms::none, p & perms::owner_all); + EXPECT_EQ(perms::none, p & perms::others_all); + + p = perms::others_read | perms::others_write | perms::others_exec; + EXPECT_EQ(perms::others_all, p); + EXPECT_EQ(perms::none, p & perms::owner_all); + EXPECT_EQ(perms::none, p & perms::group_all); +} + +TEST(path, posix_status_perms) { + // Expect /dev/null permissions to be 0666 + perms null_perm = status("/dev/null").permissions(); + perms expected = perms::owner_read|perms::owner_write|perms::group_read|perms::group_write|perms::others_read|perms::others_write; + EXPECT_EQ(expected, null_perm); + + // Expect / to be have exec flag set for everyonr + perms root_perm = status("/").permissions(); + EXPECT_NE(perms::none, root_perm&perms::owner_exec); + EXPECT_NE(perms::none, root_perm&perms::group_exec); + EXPECT_NE(perms::none, root_perm&perms::others_exec); +} + diff --git a/tests/validation/CMakeLists.txt b/tests/validation/CMakeLists.txt index cea12373..5523c3d2 100644 --- a/tests/validation/CMakeLists.txt +++ b/tests/validation/CMakeLists.txt @@ -30,7 +30,11 @@ set(VALIDATION_CUDA_SOURCES ) if(NMC_VALIDATION_DATA_DIR) - add_definitions("-DDATADIR=\"${NMC_VALIDATION_DATA_DIR}\"") + if ("${CMAKE_VERSION}" MATCHES "^3.[78].") + message(WARNING "CMake ${CMAKE_VERSION} has broken FindCUDA; omitting NMC_DATADIR define.") + else() + add_definitions("-DNMC_DATADIR=\"${NMC_VALIDATION_DATA_DIR}\"") + endif() endif() add_executable(validate.exe ${VALIDATION_SOURCES}) diff --git a/tests/validation/validate.cpp b/tests/validation/validate.cpp index e120ecc6..e186a64f 100644 --- a/tests/validation/validate.cpp +++ b/tests/validation/validate.cpp @@ -23,7 +23,13 @@ const char* usage_str = " compartments per segment\n" " -d, --min-dt=DT Run convergence tests with or with a minimumf\n" " timestep DT\n" -" -h, --help Display usage information and exit\n"; +" -h, --help Display usage information and exit\n" +"\n" +"Validation data is searched by default in the directory specified by\n" +"NMC_DATADIR at compile time. If NMC_DATADIR does not correspond to a\n" +"directory, the tests will try the paths './validation/data' and\n" +"'../validation/data'. This default path can be overridden with the\n" +"NMC_DATADIR environment variable, or with the -p command-line option.\n"; int main(int argc, char **argv) { using to::parse_opt; diff --git a/tests/validation/validation_data.cpp b/tests/validation/validation_data.cpp index 942ce8a7..7b44713b 100644 --- a/tests/validation/validation_data.cpp +++ b/tests/validation/validation_data.cpp @@ -1,4 +1,5 @@ #include <algorithm> +#include <cstdlib> #include <fstream> #include <stdexcept> #include <string> @@ -15,6 +16,38 @@ namespace mc { trace_io g_trace_io; +#ifndef NMC_DATADIR +#define NMC_DATADIR "" +#endif + +util::path trace_io::find_datadir() { + // If environment variable is set, use that in preference. + + if (const char* env_path = std::getenv("NMC_DATADIR")) { + return util::path(env_path); + } + + // Otherwise try compile-time path NMC_DATADIR and hard-coded + // relative paths below in turn, returning the first that + // corresponds to an existing directory. + + const char* paths[] = { + NMC_DATADIR, + "./validation/data", + "../validation/data" + }; + + std::error_code ec; + for (auto p: paths) { + if (util::is_directory(p, ec)) { + return util::path(p); + } + } + + // Otherwise set to empty path, and rely on command-line option. + return util::path(); +} + void trace_io::save_trace(const std::string& label, const trace_data& data, const nlohmann::json& meta) { save_trace("time", label, data, meta); } diff --git a/tests/validation/validation_data.hpp b/tests/validation/validation_data.hpp index f6a4b821..a470ebf4 100644 --- a/tests/validation/validation_data.hpp +++ b/tests/validation/validation_data.hpp @@ -10,10 +10,6 @@ #include <simple_sampler.hpp> #include <util/path.hpp> -#ifndef DATADIR -#define DATADIR "../data" -#endif - namespace nest { namespace mc { @@ -29,6 +25,12 @@ namespace mc { class trace_io { public: + // Try to find the data directory on construction. + + trace_io() { + datadir_ = find_datadir(); + } + void clear_traces() { jtraces_ = nlohmann::json::array(); } @@ -64,7 +66,7 @@ public: } } - // write traces on exit + // Write traces on exit. ~trace_io() { if (out_) { @@ -73,12 +75,19 @@ public: } private: - util::path datadir_ = DATADIR; + util::path datadir_; std::ofstream out_; nlohmann::json jtraces_ = nlohmann::json::array(); bool verbose_flag_ = false; int max_ncomp_ = 100; float min_dt_ = 0.001f; + + // Returns value of NMC_DATADIR environment variable if set, + // otherwise make a 'best-effort' search for the data directory, + // starting with NMC_DATADIR preprocessor define if defined and + // if the directory exists, or else try './validation/data' + // and '../validation/data'. + static util::path find_datadir(); }; extern trace_io g_trace_io; -- GitLab