Index: include/experimental/filesystem =================================================================== --- include/experimental/filesystem +++ include/experimental/filesystem @@ -1903,6 +1903,10 @@ } +class directory_iterator; +class recursive_directory_iterator; +class __dir_stream; + class directory_entry { typedef _VSTD_FS::path _Path; @@ -1914,7 +1918,15 @@ directory_entry(directory_entry&&) _NOEXCEPT = default; _LIBCPP_INLINE_VISIBILITY - explicit directory_entry(_Path const& __p) : __p_(__p) {} + explicit directory_entry(_Path const& __p) : __p_(__p) { + error_code __ec; + __refresh(&__ec); + } + + _LIBCPP_INLINE_VISIBILITY + directory_entry(_Path const& __p, error_code &__ec) : __p_(__p) { + __refresh(&__ec); + } ~directory_entry() {} @@ -1924,14 +1936,38 @@ _LIBCPP_INLINE_VISIBILITY void assign(_Path const& __p) { __p_ = __p; + error_code __ec; + __refresh(&__ec); + } + + _LIBCPP_INLINE_VISIBILITY + void assign(_Path const& __p, error_code& __ec) { + __p_ = __p; + __refresh(&__ec); } _LIBCPP_INLINE_VISIBILITY void replace_filename(_Path const& __p) { - __p_ = __p_.parent_path() / __p; + __p_.replace_filename(__p); + error_code __ec; + __refresh(&__ec); + } + + _LIBCPP_INLINE_VISIBILITY + void replace_filename(_Path const& __p, error_code &__ec) { + __p_ = __p_.parent_path() / __p; + __refresh(&__ec); } _LIBCPP_INLINE_VISIBILITY + void refresh() { + __refresh(); + } + + _LIBCPP_INLINE_VISIBILITY + void refresh(error_code& __ec) _NOEXCEPT { __refresh(&__ec); } + + _LIBCPP_INLINE_VISIBILITY _Path const& path() const _NOEXCEPT { return __p_; } @@ -1942,23 +1978,142 @@ } _LIBCPP_INLINE_VISIBILITY + bool exists() const { + return _VSTD_FS::exists(file_status{__get_ft()}); + } + + _LIBCPP_INLINE_VISIBILITY + bool exists(error_code& __ec) const noexcept { + return _VSTD_FS::exists(file_status{__get_ft(&__ec)}); + } + + _LIBCPP_INLINE_VISIBILITY + bool is_block_file() const { + return __get_ft() == file_type::block; + } + + _LIBCPP_INLINE_VISIBILITY + bool is_block_file(error_code& __ec) const noexcept { + return __get_ft(&__ec) == file_type::block; + } + + _LIBCPP_INLINE_VISIBILITY + bool is_character_file() const { + return __get_ft() == file_type::character; + } + + _LIBCPP_INLINE_VISIBILITY + bool is_character_file(error_code& __ec) const noexcept { + return __get_ft(&__ec) == file_type::character; + } + + _LIBCPP_INLINE_VISIBILITY + bool is_directory() const { + return __get_ft() == file_type::directory; + } + + _LIBCPP_INLINE_VISIBILITY + bool is_directory(error_code& __ec) const noexcept { + return __get_ft(&__ec) == file_type::directory; + } + + _LIBCPP_INLINE_VISIBILITY + bool is_fifo() const { + return __get_ft() == file_type::fifo; + } + + _LIBCPP_INLINE_VISIBILITY + bool is_fifo(error_code& __ec) const noexcept { + return __get_ft(&__ec) == file_type::fifo; + } + + _LIBCPP_INLINE_VISIBILITY + bool is_other() const { + return _VSTD_FS::is_other(file_status{__get_ft()}); + } + + _LIBCPP_INLINE_VISIBILITY + bool is_other(error_code& __ec) const noexcept { + return _VSTD_FS::is_other(file_status{__get_ft(&__ec)}); + } + + _LIBCPP_INLINE_VISIBILITY + bool is_regular_file() const { + return __get_ft() == file_type::regular; + } + + _LIBCPP_INLINE_VISIBILITY + bool is_regular_file(error_code& __ec) const noexcept { + return __get_ft(&__ec) == file_type::regular; + } + + _LIBCPP_INLINE_VISIBILITY + bool is_socket() const { + return __get_ft() == file_type::socket; + } + + _LIBCPP_INLINE_VISIBILITY + bool is_socket(error_code& __ec) const noexcept { + return __get_ft(&__ec) == file_type::socket; + } + + _LIBCPP_INLINE_VISIBILITY + bool is_symlink() const { + return __get_sym_ft() == file_type::symlink; + } + + _LIBCPP_INLINE_VISIBILITY + bool is_symlink(error_code& __ec) const noexcept { + return __get_sym_ft(&__ec) == file_type::symlink; + } + _LIBCPP_INLINE_VISIBILITY + uintmax_t file_size() const { + return __get_size(); + } + + _LIBCPP_INLINE_VISIBILITY + uintmax_t file_size(error_code& __ec) const noexcept { + return __get_size(&__ec); + } + + _LIBCPP_INLINE_VISIBILITY + uintmax_t hard_link_count() const { + return __get_nlink(); + } + + _LIBCPP_INLINE_VISIBILITY + uintmax_t hard_link_count(error_code& __ec) const noexcept { + return __get_nlink(&__ec); + } + + _LIBCPP_INLINE_VISIBILITY + file_time_type last_write_time() const { + return __get_write_time(); + } + + _LIBCPP_INLINE_VISIBILITY + file_time_type last_write_time(error_code& __ec) const noexcept { + return __get_write_time(&__ec); + } + + _LIBCPP_INLINE_VISIBILITY file_status status() const { - return _VSTD_FS::status(__p_); + return __get_status(); } _LIBCPP_INLINE_VISIBILITY file_status status(error_code& __ec) const _NOEXCEPT { - return _VSTD_FS::status(__p_, __ec); + return __get_status(&__ec); } _LIBCPP_INLINE_VISIBILITY file_status symlink_status() const { - return _VSTD_FS::symlink_status(__p_); + return __get_symlink_status(); } _LIBCPP_INLINE_VISIBILITY file_status symlink_status(error_code& __ec) const _NOEXCEPT { - return _VSTD_FS::symlink_status(__p_, __ec); + return __get_symlink_status(&__ec); } _LIBCPP_INLINE_VISIBILITY @@ -1990,14 +2145,230 @@ bool operator>=(directory_entry const& __rhs) const _NOEXCEPT { return __p_ >= __rhs.__p_; } + private: - _Path __p_; -}; + friend class directory_iterator; + friend class recursive_directory_iterator; + friend class __dir_stream; + + enum _CacheType : unsigned char { + _Empty, + _IterSymlink, + _IterNonSymlink, + _RefreshSymlink, + _RefreshSymlinkUnresolved, + _RefreshNonSymlink + }; + struct __cached_data { + uintmax_t __size_; + uintmax_t __nlink_; + file_time_type __write_time_; + perms __sym_perms_; + perms __non_sym_perms_; + file_type __type_; + _CacheType __cache_type_; + + _LIBCPP_INLINE_VISIBILITY + __cached_data() noexcept { __reset(); } + + _LIBCPP_INLINE_VISIBILITY + void __reset() { + __cache_type_ = _Empty; + __type_ = file_type::none; + __sym_perms_ = __non_sym_perms_ = perms::unknown; + __size_ = __nlink_ = uintmax_t(-1); + __write_time_ = file_time_type::min(); + } + }; -class directory_iterator; -class recursive_directory_iterator; -class __dir_stream; + _LIBCPP_INLINE_VISIBILITY + static __cached_data __create_iter_result(file_type __ft) { + __cached_data __data; + __data.__type_ = __ft; + __data.__cache_type_ = + __ft == file_type::symlink ? _IterSymlink : _IterNonSymlink; + return __data; + } + + _LIBCPP_INLINE_VISIBILITY + void __assign_iter_entry(_Path&& __p, __cached_data __dt) { + __p_ = std::move(__p); + __data_ = __dt; + } + + _LIBCPP_FUNC_VIS + error_code __do_refresh() noexcept; + + _LIBCPP_INLINE_VISIBILITY + static bool __is_dne_error(error_code const& __ec) { + if (!__ec) + return true; + switch (static_cast(__ec.value())) { + case errc::no_such_file_or_directory: + case errc::not_a_directory: + return true; + default: + return false; + } + } + + _LIBCPP_INLINE_VISIBILITY + void __handle_error(const char* __msg, error_code* __dest_ec, + error_code const& __ec, + bool __allow_dne = false) const { + if (__dest_ec) { + *__dest_ec = __ec; + return; + } + if (__ec && (!__allow_dne || !__is_dne_error(__ec))) + __throw_filesystem_error(__msg, __p_, _Path{}, __ec); + } + + _LIBCPP_INLINE_VISIBILITY + void __refresh(error_code* __ec = nullptr) { + __handle_error("refresh", __ec, __do_refresh(), /*allow_dne*/ true); + } + + _LIBCPP_INLINE_VISIBILITY + file_type __get_sym_ft(error_code *__ec = nullptr) const { + switch (__data_.__cache_type_) { + case _Empty: + return __symlink_status(__p_, __ec).type(); + case _IterSymlink: + case _RefreshSymlink: + case _RefreshSymlinkUnresolved: + if (__ec) + __ec->clear(); + return file_type::symlink; + case _IterNonSymlink: + case _RefreshNonSymlink: + file_status __st(__data_.__type_); + if (__ec && !_VSTD_FS::exists(__st)) + *__ec = make_error_code(errc::no_such_file_or_directory); + else if (__ec) + __ec->clear(); + return __data_.__type_; + } + } + + _LIBCPP_INLINE_VISIBILITY + file_type __get_ft(error_code *__ec = nullptr) const { + switch (__data_.__cache_type_) { + case _Empty: + case _IterSymlink: + case _RefreshSymlinkUnresolved: + return __status(__p_, __ec).type(); + case _IterNonSymlink: + case _RefreshNonSymlink: + case _RefreshSymlink: { + file_status __st(__data_.__type_); + if (__ec && !_VSTD_FS::exists(__st)) + *__ec = make_error_code(errc::no_such_file_or_directory); + else if (__ec) + __ec->clear(); + return __data_.__type_; + } + } + } + + _LIBCPP_INLINE_VISIBILITY + file_status __get_status(error_code *__ec = nullptr) const { + switch (__data_.__cache_type_) { + case _Empty: + case _IterNonSymlink: + case _IterSymlink: + case _RefreshSymlinkUnresolved: + return __status(__p_, __ec); + case _RefreshNonSymlink: + case _RefreshSymlink: + return file_status(__get_ft(__ec), __data_.__non_sym_perms_); + } + } + + _LIBCPP_INLINE_VISIBILITY + file_status __get_symlink_status(error_code *__ec = nullptr) const { + switch (__data_.__cache_type_) { + case _Empty: + case _IterNonSymlink: + case _IterSymlink: + return __symlink_status(__p_, __ec); + case _RefreshNonSymlink: + return file_status(__get_sym_ft(__ec), __data_.__non_sym_perms_); + case _RefreshSymlink: + case _RefreshSymlinkUnresolved: + return file_status(__get_sym_ft(__ec), __data_.__sym_perms_); + } + } + + + _LIBCPP_INLINE_VISIBILITY + uintmax_t __get_size(error_code *__ec = nullptr) const { + switch (__data_.__cache_type_) { + case _Empty: + case _IterNonSymlink: + case _IterSymlink: + case _RefreshSymlinkUnresolved: + return _VSTD_FS::__file_size(__p_, __ec); + case _RefreshSymlink: + case _RefreshNonSymlink: { + error_code __m_ec; + file_status __st(__get_ft(&__m_ec)); + __handle_error("directory_entry::file_size", __ec, __m_ec); + if (_VSTD_FS::exists(__st) && !_VSTD_FS::is_regular_file(__st)) { + errc __err_kind = _VSTD_FS::is_directory(__st) ? errc::is_a_directory + : errc::not_supported; + __handle_error("directory_entry::file_size", __ec, + make_error_code(__err_kind)); + } + return __data_.__size_; + } + } + } + + _LIBCPP_INLINE_VISIBILITY + uintmax_t __get_nlink(error_code *__ec = nullptr) const { + switch (__data_.__cache_type_) { + case _Empty: + case _IterNonSymlink: + case _IterSymlink: + case _RefreshSymlinkUnresolved: + return _VSTD_FS::__hard_link_count(__p_, __ec); + case _RefreshSymlink: + case _RefreshNonSymlink: { + error_code __m_ec; + (void)__get_ft(&__m_ec); + __handle_error("directory_entry::hard_link_count", __ec, __m_ec); + return __data_.__nlink_; + } + } + } + + _LIBCPP_INLINE_VISIBILITY + file_time_type __get_write_time(error_code *__ec = nullptr) const { + switch (__data_.__cache_type_) { + case _Empty: + case _IterNonSymlink: + case _IterSymlink: + case _RefreshSymlinkUnresolved: + return _VSTD_FS::__last_write_time(__p_, __ec); + case _RefreshSymlink: + case _RefreshNonSymlink: { + error_code __m_ec; + file_status __st(__get_ft(&__m_ec)); + __handle_error("directory_entry::last_write_time", __ec, __m_ec); + if (_VSTD_FS::exists(__st) && + __data_.__write_time_ == file_time_type::min()) + __handle_error("directory_entry::last_write_time", __ec, + make_error_code(errc::value_too_large)); + return __data_.__write_time_; + } + } + } +private: + _Path __p_; + __cached_data __data_; +}; class __dir_element_proxy { public: Index: src/experimental/filesystem/directory_iterator.cpp =================================================================== --- src/experimental/filesystem/directory_iterator.cpp +++ src/experimental/filesystem/directory_iterator.cpp @@ -17,32 +17,43 @@ #endif #include +#include "filesystem_common.h" + _LIBCPP_BEGIN_NAMESPACE_EXPERIMENTAL_FILESYSTEM -namespace { namespace detail { +namespace detail { +namespace { #if !defined(_LIBCPP_WIN32API) -inline error_code capture_errno() { - _LIBCPP_ASSERT(errno, "Expected errno to be non-zero"); - return error_code{errno, std::generic_category()}; +template +static file_type get_file_type(DirEntT *ent, int) { + switch (ent->d_type) { + case DT_BLK: + return file_type::block; + case DT_CHR: + return file_type::character; + case DT_DIR: + return file_type::directory; + case DT_FIFO: + return file_type::fifo; + case DT_LNK: + return file_type::symlink; + case DT_REG: + return file_type::regular; + case DT_SOCK: + return file_type::socket; + case DT_UNKNOWN: + return file_type::unknown; + } + return file_type::none; } -#endif - -template -inline bool set_or_throw(std::error_code& my_ec, - std::error_code* user_ec, - const char* msg, Args&&... args) -{ - if (user_ec) { - *user_ec = my_ec; - return true; - } - __throw_filesystem_error(msg, std::forward(args)..., my_ec); - return false; +template +static file_type get_file_type(DirEntT *ent, long) { + return file_type::unknown; } -#if !defined(_LIBCPP_WIN32API) -inline path::string_type posix_readdir(DIR *dir_stream, error_code& ec) { +static pair +posix_readdir(DIR *dir_stream, error_code& ec) { struct dirent* dir_entry_ptr = nullptr; errno = 0; // zero errno in order to detect errors ec.clear(); @@ -51,18 +62,38 @@ ec = capture_errno(); return {}; } else { - return dir_entry_ptr->d_name; + return {dir_entry_ptr->d_name, get_file_type(dir_entry_ptr, 0)}; } } +#else + +static file_type get_file_type(const WIN32_FIND_DATA& data) { + //auto attrs = data.dwFileAttributes; + // FIXME(EricWF) + return file_type::unknown; +} +static uintmax_t get_file_size(const WIN32_FIND_DATA& data) { + return (data.nFileSizeHight * (MAXDWORD+1)) + data.nFileSizeLow; +} +static file_time_type get_write_time(const WIN32_FIND_DATA& data) { + ULARGE_INTEGER tmp; + FILETIME& time = data.ftLastWriteTime; + tmp.u.LowPart = time.dwLowDateTime; + tmp.u.HighPart = time.dwHighDateTime; + return file_time_type(file_time_type::duration(time.QuadPart)); +} + #endif -}} // namespace detail +} // namespace +} // namespace detail using detail::set_or_throw; #if defined(_LIBCPP_WIN32API) class __dir_stream { public: + __dir_stream() = delete; __dir_stream& operator=(const __dir_stream&) = delete; @@ -74,7 +105,7 @@ __dir_stream(const path& root, directory_options opts, error_code& ec) : __stream_(INVALID_HANDLE_VALUE), __root_(root) { - __stream_ = ::FindFirstFile(root.c_str(), &__data_); + __stream_ = ::FindFirstFileEx(root.c_str(), &__data_); if (__stream_ == INVALID_HANDLE_VALUE) { ec = error_code(::GetLastError(), std::generic_category()); const bool ignore_permission_denied = @@ -97,7 +128,14 @@ while (::FindNextFile(__stream_, &__data_)) { if (!strcmp(__data_.cFileName, ".") || strcmp(__data_.cFileName, "..")) continue; - __entry_.assign(__root_ / __data_.cFileName); + // FIXME: Cache more of this + //directory_entry::__cached_data cdata; + //cdata.__type_ = get_file_type(__data_); + //cdata.__size_ = get_file_size(__data_); + //cdata.__write_time_ = get_write_time(__data_); + __entry_.__assign_iter_entry( + __root_ / __data_.cFileName, + directory_entry::__create_iter_result(get_file_type(__data))); return true; } ec = error_code(::GetLastError(), std::generic_category()); @@ -157,15 +195,18 @@ bool advance(error_code &ec) { while (true) { - auto str = detail::posix_readdir(__stream_, ec); + auto str_type_pair = detail::posix_readdir(__stream_, ec); + auto& str = str_type_pair.first; if (str == "." || str == "..") { continue; } else if (ec || str.empty()) { close(); return false; } else { - __entry_.assign(__root_ / str); - return true; + __entry_.__assign_iter_entry( + __root_ / str, + directory_entry::__create_iter_result(str_type_pair.second)); + return true; } } } Index: src/experimental/filesystem/filesystem_common.h =================================================================== --- /dev/null +++ src/experimental/filesystem/filesystem_common.h @@ -0,0 +1,296 @@ +//===----------------------------------------------------------------------===//// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===//// + +#ifndef FILESYSTEM_COMMON_H +#define FILESYSTEM_COMMON_H + +#include "experimental/__config" +#include "chrono" +#include "cstdlib" +#include "climits" + +#include +#include +#include +#include /* values for fchmodat */ + +#include + +#if (__APPLE__) +#if defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) +#if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 101300 +#define _LIBCXX_USE_UTIMENSAT +#endif +#elif defined(__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__) +#if __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ >= 110000 +#define _LIBCXX_USE_UTIMENSAT +#endif +#elif defined(__ENVIRONMENT_TV_OS_VERSION_MIN_REQUIRED__) +#if __ENVIRONMENT_TV_OS_VERSION_MIN_REQUIRED__ >= 110000 +#define _LIBCXX_USE_UTIMENSAT +#endif +#elif defined(__ENVIRONMENT_WATCH_OS_VERSION_MIN_REQUIRED__) +#if __ENVIRONMENT_WATCH_OS_VERSION_MIN_REQUIRED__ >= 40000 +#define _LIBCXX_USE_UTIMENSAT +#endif +#endif // __ENVIRONMENT_.*_VERSION_MIN_REQUIRED__ +#else +// We can use the presence of UTIME_OMIT to detect platforms that provide +// utimensat. +#if defined(UTIME_OMIT) +#define _LIBCXX_USE_UTIMENSAT +#endif +#endif // __APPLE__ + +#if !defined(_LIBCXX_USE_UTIMENSAT) +#include // for ::utimes as used in __last_write_time +#endif + +#if !defined(UTIME_OMIT) +#include // for ::utimes as used in __last_write_time +#endif + +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#endif + +_LIBCPP_BEGIN_NAMESPACE_EXPERIMENTAL_FILESYSTEM + +namespace detail { +namespace { + +std::error_code capture_errno() { + _LIBCPP_ASSERT(errno, "Expected errno to be non-zero"); + return std::error_code(errno, std::generic_category()); +} + +void set_or_throw(std::error_code const& m_ec, std::error_code* ec, + const char* msg, path const& p = {}, path const& p2 = {}) { + if (ec) { + *ec = m_ec; + } else { + string msg_s("std::experimental::filesystem::"); + msg_s += msg; + __throw_filesystem_error(msg_s, p, p2, m_ec); + } +} + +void set_or_throw(std::error_code* ec, const char* msg, path const& p = {}, + path const& p2 = {}) { + return set_or_throw(capture_errno(), ec, msg, p, p2); +} + +namespace time_util { + +using namespace chrono; + +template ::value> +struct fs_time_util_base { + static constexpr auto max_seconds = + duration_cast(FileTimeT::duration::max()).count(); + + static constexpr auto max_nsec = + duration_cast(FileTimeT::duration::max() - + seconds(max_seconds)) + .count(); + + static constexpr auto min_seconds = + duration_cast(FileTimeT::duration::min()).count(); + + static constexpr auto min_nsec_timespec = + duration_cast( + (FileTimeT::duration::min() - seconds(min_seconds)) + seconds(1)) + .count(); + + // Static assert that these values properly round trip. + static_assert((seconds(min_seconds) + + duration_cast(nanoseconds(min_nsec_timespec))) - + duration_cast(seconds(1)) == + FileTimeT::duration::min(), + ""); +}; + +template +struct fs_time_util_base { + static const long long max_seconds; + static const long long max_nsec; + static const long long min_seconds; + static const long long min_nsec_timespec; +}; + +template +const long long fs_time_util_base::max_seconds = + duration_cast(FileTimeT::duration::max()).count(); + +template +const long long fs_time_util_base::max_nsec = + duration_cast(FileTimeT::duration::max() - + seconds(max_seconds)) + .count(); + +template +const long long fs_time_util_base::min_seconds = + duration_cast(FileTimeT::duration::min()).count(); + +template +const long long fs_time_util_base::min_nsec_timespec = + duration_cast( + (FileTimeT::duration::min() - seconds(min_seconds)) + seconds(1)) + .count(); + +template +struct fs_time_util : fs_time_util_base { + using Base = fs_time_util_base; + using Base::max_nsec; + using Base::max_seconds; + using Base::min_nsec_timespec; + using Base::min_seconds; + +public: + template + static bool checked_set(CType* out, ChronoType time) { + using Lim = numeric_limits; + if (time > Lim::max() || time < Lim::min()) + return false; + *out = static_cast(time); + return true; + } + + static _LIBCPP_CONSTEXPR_AFTER_CXX11 bool is_representable(TimeSpecT tm) { + if (tm.tv_sec >= 0) { + return (tm.tv_sec < max_seconds) || + (tm.tv_sec == max_seconds && tm.tv_nsec <= max_nsec); + } else if (tm.tv_sec == (min_seconds - 1)) { + return tm.tv_nsec >= min_nsec_timespec; + } else { + return (tm.tv_sec >= min_seconds); + } + } + + static _LIBCPP_CONSTEXPR_AFTER_CXX11 bool is_representable(FileTimeT tm) { + auto secs = duration_cast(tm.time_since_epoch()); + auto nsecs = duration_cast(tm.time_since_epoch() - secs); + if (nsecs.count() < 0) { + secs = secs + seconds(1); + nsecs = nsecs + seconds(1); + } + using TLim = numeric_limits; + if (secs.count() >= 0) + return secs.count() <= TLim::max(); + return secs.count() >= TLim::min(); + } + + static _LIBCPP_CONSTEXPR_AFTER_CXX11 FileTimeT + convert_timespec(TimeSpecT tm) { + auto adj_msec = duration_cast(nanoseconds(tm.tv_nsec)); + if (tm.tv_sec >= 0) { + auto Dur = seconds(tm.tv_sec) + microseconds(adj_msec); + return FileTimeT(Dur); + } else if (duration_cast(nanoseconds(tm.tv_nsec)).count() == + 0) { + return FileTimeT(seconds(tm.tv_sec)); + } else { // tm.tv_sec < 0 + auto adj_subsec = + duration_cast(seconds(1) - nanoseconds(tm.tv_nsec)); + auto Dur = seconds(tm.tv_sec + 1) - adj_subsec; + return FileTimeT(Dur); + } + } + + template + static bool set_times_checked(TimeT* sec_out, SubSecT* subsec_out, + FileTimeT tp) { + auto dur = tp.time_since_epoch(); + auto sec_dur = duration_cast(dur); + auto subsec_dur = duration_cast(dur - sec_dur); + // The tv_nsec and tv_usec fields must not be negative so adjust accordingly + if (subsec_dur.count() < 0) { + if (sec_dur.count() > min_seconds) { + sec_dur -= seconds(1); + subsec_dur += seconds(1); + } else { + subsec_dur = SubSecDurT::zero(); + } + } + return checked_set(sec_out, sec_dur.count()) && + checked_set(subsec_out, subsec_dur.count()); + } +}; + +} // namespace time_util + + +using TimeSpec = struct timespec; +using StatT = struct stat; + +using FSTime = time_util::fs_time_util; + +#if defined(__APPLE__) +TimeSpec extract_mtime(StatT const& st) { return st.st_mtimespec; } +TimeSpec extract_atime(StatT const& st) { return st.st_atimespec; } +#else +TimeSpec extract_mtime(StatT const& st) { return st.st_mtim; } +TimeSpec extract_atime(StatT const& st) { return st.st_atim; } +#endif + +#if !defined(_LIBCXX_USE_UTIMENSAT) +using TimeStruct = struct ::timeval; +using TimeStructArray = TimeStruct[2]; +#else +using TimeStruct = struct ::timespec; +using TimeStructArray = TimeStruct[2]; +#endif + +bool SetFileTimes(const path& p, TimeStructArray const& TS, + std::error_code& ec) { +#if !defined(_LIBCXX_USE_UTIMENSAT) + if (::utimes(p.c_str(), TS) == -1) +#else + if (::utimensat(AT_FDCWD, p.c_str(), TS, 0) == -1) +#endif + { + ec = capture_errno(); + return true; + } + return false; +} + +void SetTimeStructTo(TimeStruct& TS, TimeSpec ToTS) { + using namespace chrono; + TS.tv_sec = ToTS.tv_sec; +#if !defined(_LIBCXX_USE_UTIMENSAT) + TS.tv_usec = duration_cast(nanoseconds(ToTS.tv_nsec)).count(); +#else + TS.tv_nsec = ToTS.tv_nsec; +#endif +} + +bool SetTimeStructTo(TimeStruct& TS, file_time_type NewTime) { + using namespace chrono; +#if !defined(_LIBCXX_USE_UTIMENSAT) + return !FSTime::set_times_checked(&TS.tv_sec, &TS.tv_usec, + NewTime); +#else + return !FSTime::set_times_checked(&TS.tv_sec, &TS.tv_nsec, + NewTime); +#endif +} + +} // namespace +} // end namespace detail + +_LIBCPP_END_NAMESPACE_EXPERIMENTAL_FILESYSTEM + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + +#endif // FILESYSTEM_COMMON_H Index: src/experimental/filesystem/filesystem_time_helper.h =================================================================== --- src/experimental/filesystem/filesystem_time_helper.h +++ /dev/null @@ -1,173 +0,0 @@ -//===----------------------------------------------------------------------===//// -// -// The LLVM Compiler Infrastructure -// -// This file is dual licensed under the MIT and the University of Illinois Open -// Source Licenses. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===//// - -#ifndef FILESYSTEM_TIME_HELPER_H -#define FILESYSTEM_TIME_HELPER_H - -#include "experimental/__config" -#include "chrono" -#include "cstdlib" -#include "climits" - -#include -#include -#if !defined(UTIME_OMIT) -#include // for ::utimes as used in __last_write_time -#endif - -_LIBCPP_BEGIN_NAMESPACE_EXPERIMENTAL_FILESYSTEM - -namespace time_detail { namespace { - -using namespace chrono; - -template ::value> -struct fs_time_util_base { - static constexpr auto max_seconds = - duration_cast(FileTimeT::duration::max()).count(); - - static constexpr auto max_nsec = - duration_cast(FileTimeT::duration::max() - - seconds(max_seconds)) - .count(); - - static constexpr auto min_seconds = - duration_cast(FileTimeT::duration::min()).count(); - - static constexpr auto min_nsec_timespec = - duration_cast( - (FileTimeT::duration::min() - seconds(min_seconds)) + seconds(1)) - .count(); - - // Static assert that these values properly round trip. - static_assert((seconds(min_seconds) + - duration_cast(nanoseconds(min_nsec_timespec))) - - duration_cast(seconds(1)) == - FileTimeT::duration::min(), - ""); -}; - -template -struct fs_time_util_base { - static const long long max_seconds; - static const long long max_nsec; - static const long long min_seconds; - static const long long min_nsec_timespec; -}; - -template -const long long fs_time_util_base::max_seconds = - duration_cast(FileTimeT::duration::max()).count(); - -template -const long long fs_time_util_base::max_nsec = - duration_cast(FileTimeT::duration::max() - - seconds(max_seconds)) - .count(); - -template -const long long fs_time_util_base::min_seconds = - duration_cast(FileTimeT::duration::min()).count(); - -template -const long long fs_time_util_base::min_nsec_timespec = - duration_cast((FileTimeT::duration::min() - - seconds(min_seconds)) + - seconds(1)) - .count(); - -template -struct fs_time_util : fs_time_util_base { - using Base = fs_time_util_base; - using Base::max_nsec; - using Base::max_seconds; - using Base::min_nsec_timespec; - using Base::min_seconds; - -public: - template - static bool checked_set(CType* out, ChronoType time) { - using Lim = numeric_limits; - if (time > Lim::max() || time < Lim::min()) - return false; - *out = static_cast(time); - return true; - } - - static _LIBCPP_CONSTEXPR_AFTER_CXX11 bool is_representable(TimeSpecT tm) { - if (tm.tv_sec >= 0) { - return (tm.tv_sec < max_seconds) || - (tm.tv_sec == max_seconds && tm.tv_nsec <= max_nsec); - } else if (tm.tv_sec == (min_seconds - 1)) { - return tm.tv_nsec >= min_nsec_timespec; - } else { - return (tm.tv_sec >= min_seconds); - } - } - - static _LIBCPP_CONSTEXPR_AFTER_CXX11 bool is_representable(FileTimeT tm) { - auto secs = duration_cast(tm.time_since_epoch()); - auto nsecs = duration_cast(tm.time_since_epoch() - secs); - if (nsecs.count() < 0) { - secs = secs + seconds(1); - nsecs = nsecs + seconds(1); - } - using TLim = numeric_limits; - if (secs.count() >= 0) - return secs.count() <= TLim::max(); - return secs.count() >= TLim::min(); - } - - static _LIBCPP_CONSTEXPR_AFTER_CXX11 FileTimeT - convert_timespec(TimeSpecT tm) { - auto adj_msec = duration_cast(nanoseconds(tm.tv_nsec)); - if (tm.tv_sec >= 0) { - auto Dur = seconds(tm.tv_sec) + microseconds(adj_msec); - return FileTimeT(Dur); - } else if (duration_cast(nanoseconds(tm.tv_nsec)).count() == - 0) { - return FileTimeT(seconds(tm.tv_sec)); - } else { // tm.tv_sec < 0 - auto adj_subsec = - duration_cast(seconds(1) - nanoseconds(tm.tv_nsec)); - auto Dur = seconds(tm.tv_sec + 1) - adj_subsec; - return FileTimeT(Dur); - } - } - - template - static bool set_times_checked(TimeT* sec_out, SubSecT* subsec_out, - FileTimeT tp) { - using namespace chrono; - auto dur = tp.time_since_epoch(); - auto sec_dur = duration_cast(dur); - auto subsec_dur = duration_cast(dur - sec_dur); - // The tv_nsec and tv_usec fields must not be negative so adjust accordingly - if (subsec_dur.count() < 0) { - if (sec_dur.count() > min_seconds) { - sec_dur -= seconds(1); - subsec_dur += seconds(1); - } else { - subsec_dur = SubSecDurT::zero(); - } - } - return checked_set(sec_out, sec_dur.count()) && - checked_set(subsec_out, subsec_dur.count()); - } -}; - -} // end namespace -} // end namespace time_detail - -using time_detail::fs_time_util; - -_LIBCPP_END_NAMESPACE_EXPERIMENTAL_FILESYSTEM - -#endif // FILESYSTEM_TIME_HELPER_H Index: src/experimental/filesystem/operations.cpp =================================================================== --- src/experimental/filesystem/operations.cpp +++ src/experimental/filesystem/operations.cpp @@ -17,42 +17,18 @@ #include "cstdlib" #include "climits" -#include "filesystem_time_helper.h" +#include "filesystem_common.h" #include #include #include #include /* values for fchmodat */ +#include -#if (__APPLE__) -#if defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) -#if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 101300 -#define _LIBCXX_USE_UTIMENSAT -#endif -#elif defined(__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__) -#if __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ >= 110000 -#define _LIBCXX_USE_UTIMENSAT -#endif -#elif defined(__ENVIRONMENT_TV_OS_VERSION_MIN_REQUIRED__) -#if __ENVIRONMENT_TV_OS_VERSION_MIN_REQUIRED__ >= 110000 -#define _LIBCXX_USE_UTIMENSAT -#endif -#elif defined(__ENVIRONMENT_WATCH_OS_VERSION_MIN_REQUIRED__) -#if __ENVIRONMENT_WATCH_OS_VERSION_MIN_REQUIRED__ >= 40000 -#define _LIBCXX_USE_UTIMENSAT -#endif -#endif // __ENVIRONMENT_.*_VERSION_MIN_REQUIRED__ -#else -// We can use the presence of UTIME_OMIT to detect platforms that provide -// utimensat. -#if defined(UTIME_OMIT) -#define _LIBCXX_USE_UTIMENSAT -#endif -#endif // __APPLE__ - -#if !defined(_LIBCXX_USE_UTIMENSAT) -#include // for ::utimes as used in __last_write_time +#ifdef NDEBUG +#undef NDEBUG #endif +#include _LIBCPP_BEGIN_NAMESPACE_EXPERIMENTAL_FILESYSTEM @@ -310,96 +286,78 @@ using value_type = path::value_type; using string_type = path::string_type; -inline std::error_code capture_errno() { - _LIBCPP_ASSERT(errno, "Expected errno to be non-zero"); - return std::error_code(errno, std::generic_category()); -} - -void set_or_throw(std::error_code const& m_ec, std::error_code* ec, - const char* msg, path const& p = {}, path const& p2 = {}) -{ - if (ec) { - *ec = m_ec; - } else { - string msg_s("std::experimental::filesystem::"); - msg_s += msg; - __throw_filesystem_error(msg_s, p, p2, m_ec); - } -} - -void set_or_throw(std::error_code* ec, const char* msg, - path const& p = {}, path const& p2 = {}) -{ - return set_or_throw(capture_errno(), ec, msg, p, p2); -} - -perms posix_get_perms(const struct ::stat & st) noexcept { - return static_cast(st.st_mode) & perms::mask; +perms posix_get_perms(const struct ::stat& st) noexcept { + return static_cast(st.st_mode) & perms::mask; } ::mode_t posix_convert_perms(perms prms) { - return static_cast< ::mode_t>(prms & perms::mask); + return static_cast< ::mode_t>(prms & perms::mask); } file_status create_file_status(std::error_code& m_ec, path const& p, - struct ::stat& path_stat, - std::error_code* ec) -{ - if (ec) *ec = m_ec; - if (m_ec && (m_ec.value() == ENOENT || m_ec.value() == ENOTDIR)) { - return file_status(file_type::not_found); - } - else if (m_ec) { - set_or_throw(m_ec, ec, "posix_stat", p); - return file_status(file_type::none); - } - // else - - file_status fs_tmp; - auto const mode = path_stat.st_mode; - if (S_ISLNK(mode)) fs_tmp.type(file_type::symlink); - else if (S_ISREG(mode)) fs_tmp.type(file_type::regular); - else if (S_ISDIR(mode)) fs_tmp.type(file_type::directory); - else if (S_ISBLK(mode)) fs_tmp.type(file_type::block); - else if (S_ISCHR(mode)) fs_tmp.type(file_type::character); - else if (S_ISFIFO(mode)) fs_tmp.type(file_type::fifo); - else if (S_ISSOCK(mode)) fs_tmp.type(file_type::socket); - else fs_tmp.type(file_type::unknown); - - fs_tmp.permissions(detail::posix_get_perms(path_stat)); - return fs_tmp; -} - -file_status posix_stat(path const & p, struct ::stat& path_stat, - std::error_code* ec) -{ - std::error_code m_ec; - if (::stat(p.c_str(), &path_stat) == -1) - m_ec = detail::capture_errno(); - return create_file_status(m_ec, p, path_stat, ec); + struct ::stat& path_stat, std::error_code* ec) { + if (ec) + *ec = m_ec; + // assert(m_ec.value() != ENOTDIR); + if (m_ec && (m_ec.value() == ENOENT || m_ec.value() == ENOTDIR)) { + return file_status(file_type::not_found); + } else if (m_ec) { + set_or_throw(m_ec, ec, "posix_stat", p); + return file_status(file_type::none); + } + // else + + file_status fs_tmp; + auto const mode = path_stat.st_mode; + if (S_ISLNK(mode)) + fs_tmp.type(file_type::symlink); + else if (S_ISREG(mode)) + fs_tmp.type(file_type::regular); + else if (S_ISDIR(mode)) + fs_tmp.type(file_type::directory); + else if (S_ISBLK(mode)) + fs_tmp.type(file_type::block); + else if (S_ISCHR(mode)) + fs_tmp.type(file_type::character); + else if (S_ISFIFO(mode)) + fs_tmp.type(file_type::fifo); + else if (S_ISSOCK(mode)) + fs_tmp.type(file_type::socket); + else + fs_tmp.type(file_type::unknown); + + fs_tmp.permissions(detail::posix_get_perms(path_stat)); + return fs_tmp; +} + +file_status posix_stat(path const& p, struct ::stat& path_stat, + std::error_code* ec) { + std::error_code m_ec; + if (::stat(p.c_str(), &path_stat) == -1) + m_ec = detail::capture_errno(); + return create_file_status(m_ec, p, path_stat, ec); } -file_status posix_stat(path const & p, std::error_code* ec) { - struct ::stat path_stat; - return posix_stat(p, path_stat, ec); +file_status posix_stat(path const& p, std::error_code* ec) { + struct ::stat path_stat; + return posix_stat(p, path_stat, ec); } -file_status posix_lstat(path const & p, struct ::stat & path_stat, - std::error_code* ec) -{ - std::error_code m_ec; - if (::lstat(p.c_str(), &path_stat) == -1) - m_ec = detail::capture_errno(); - return create_file_status(m_ec, p, path_stat, ec); +file_status posix_lstat(path const& p, struct ::stat& path_stat, + std::error_code* ec) { + std::error_code m_ec; + if (::lstat(p.c_str(), &path_stat) == -1) + m_ec = detail::capture_errno(); + return create_file_status(m_ec, p, path_stat, ec); } -file_status posix_lstat(path const & p, std::error_code* ec) { - struct ::stat path_stat; - return posix_lstat(p, path_stat, ec); +file_status posix_lstat(path const& p, std::error_code* ec) { + struct ::stat path_stat; + return posix_lstat(p, path_stat, ec); } bool stat_equivalent(struct ::stat& st1, struct ::stat& st2) { - return (st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino); + return (st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino); } // DETAIL::MISC @@ -622,7 +580,6 @@ __create_symlink(real_path, new_symlink, ec); } - bool __create_directories(const path& p, std::error_code *ec) { std::error_code m_ec; @@ -755,10 +712,12 @@ struct ::stat st; file_status fst = detail::posix_stat(p, st, &m_ec); if (!exists(fst) || !is_regular_file(fst)) { - if (!m_ec) - m_ec = make_error_code(errc::not_supported); - set_or_throw(m_ec, ec, "file_size", p); - return static_cast(-1); + errc error_kind = + is_directory(fst) ? errc::is_a_directory : errc::not_supported; + if (!m_ec) + m_ec = make_error_code(error_kind); + set_or_throw(m_ec, ec, "file_size", p); + return static_cast(-1); } // is_regular_file(p) == true if (ec) ec->clear(); @@ -806,25 +765,18 @@ _LIBCPP_UNREACHABLE(); } - -namespace detail { namespace { - -using TimeSpec = struct timespec; -using StatT = struct stat; - -#if defined(__APPLE__) -TimeSpec extract_mtime(StatT const& st) { return st.st_mtimespec; } -__attribute__((unused)) // Suppress warning -TimeSpec extract_atime(StatT const& st) { return st.st_atimespec; } -#else -TimeSpec extract_mtime(StatT const& st) { return st.st_mtim; } -__attribute__((unused)) // Suppress warning -TimeSpec extract_atime(StatT const& st) { return st.st_atim; } -#endif - -}} // end namespace detail - -using FSTime = fs_time_util; +static file_time_type __extract_last_write_time(path const& p, + const struct ::stat& st, + error_code *ec) { + using detail::FSTime; + auto ts = detail::extract_mtime(st); + if (!FSTime::is_representable(ts)) { + set_or_throw(make_error_code(errc::value_too_large), ec, "last_write_time", + p); + return file_time_type::min(); + } + return FSTime::convert_timespec(ts); +} file_time_type __last_write_time(const path& p, std::error_code *ec) { @@ -837,21 +789,17 @@ return file_time_type::min(); } if (ec) ec->clear(); - auto ts = detail::extract_mtime(st); - if (!FSTime::is_representable(ts)) { - set_or_throw(error_code(EOVERFLOW, generic_category()), ec, - "last_write_time", p); - return file_time_type::min(); - } - return FSTime::convert_timespec(ts); + return __extract_last_write_time(p, st, ec); } void __last_write_time(const path& p, file_time_type new_time, std::error_code *ec) { using namespace std::chrono; - std::error_code m_ec; + using namespace detail; + std::error_code m_ec; + TimeStructArray tbuf; #if !defined(_LIBCXX_USE_UTIMENSAT) // This implementation has a race condition between determining the // last access time and attempting to set it to the same value using @@ -862,37 +810,18 @@ set_or_throw(m_ec, ec, "last_write_time", p); return; } - auto atime = detail::extract_atime(st); - struct ::timeval tbuf[2]; - tbuf[0].tv_sec = atime.tv_sec; - tbuf[0].tv_usec = duration_cast(nanoseconds(atime.tv_nsec)).count(); - const bool overflowed = !FSTime::set_times_checked( - &tbuf[1].tv_sec, &tbuf[1].tv_usec, new_time); - - if (overflowed) { - set_or_throw(make_error_code(errc::invalid_argument), ec, - "last_write_time", p); - return; - } - if (::utimes(p.c_str(), tbuf) == -1) { - m_ec = detail::capture_errno(); - } + SetTimeStructTo(tbuf[0], detail::extract_atime(st)); #else - struct ::timespec tbuf[2]; tbuf[0].tv_sec = 0; tbuf[0].tv_nsec = UTIME_OMIT; - - const bool overflowed = !FSTime::set_times_checked( - &tbuf[1].tv_sec, &tbuf[1].tv_nsec, new_time); - if (overflowed) { - set_or_throw(make_error_code(errc::invalid_argument), - ec, "last_write_time", p); - return; - } - if (::utimensat(AT_FDCWD, p.c_str(), tbuf, 0) == -1) { - m_ec = detail::capture_errno(); - } #endif + if (SetTimeStructTo(tbuf[1], new_time)) { + set_or_throw(make_error_code(errc::invalid_argument), ec, + "last_write_time", p); + return; + } + + SetFileTimes(p, tbuf, m_ec); if (m_ec) set_or_throw(m_ec, ec, "last_write_time", p); else if (ec) @@ -1116,8 +1045,6 @@ return result.lexically_normal(); } - - /////////////////////////////////////////////////////////////////////////////// // path definitions /////////////////////////////////////////////////////////////////////////////// @@ -1468,5 +1395,105 @@ return *this; } +/////////////////////////////////////////////////////////////////////////////// +// directory entry definitions +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _LIBCPP_WIN32API +error_code directory_entry::__do_refresh() noexcept { + __data_.__reset(); + error_code failure_ec; + + struct ::stat full_st; + file_status st = detail::posix_lstat(__p_, full_st, &failure_ec); + if (!status_known(st)) { + __data_.__reset(); + return failure_ec; + } + + if (!_VSTD_FS::exists(st) || !_VSTD_FS::is_symlink(st)) { + __data_.__cache_type_ = directory_entry::_RefreshNonSymlink; + __data_.__type_ = st.type(); + __data_.__non_sym_perms_ = st.permissions(); + } else { // we have a symlink + __data_.__sym_perms_ = st.permissions(); + // Get the information about the linked entity. + // Ignore errors from stat, since we don't want errors regarding symlink + // resolution to be reported to the user. + error_code ignored_ec; + st = detail::posix_stat(__p_, full_st, &ignored_ec); + + __data_.__type_ = st.type(); + __data_.__non_sym_perms_ = st.permissions(); + + // If we failed to resolve the link, then only partially populate the + // cache. + if (!status_known(st)) { + __data_.__cache_type_ = directory_entry::_RefreshSymlinkUnresolved; + return error_code{}; + } + // Otherwise, we resolved the link as not existing. That's OK. + __data_.__cache_type_ = directory_entry::_RefreshSymlink; + } + + if (_VSTD_FS::is_regular_file(st)) + __data_.__size_ = static_cast(full_st.st_size); + + if (_VSTD_FS::exists(st)) { + __data_.__nlink_ = static_cast(full_st.st_nlink); + + // Attempt to extract the mtime, and fail if it's not representable using + // file_time_type. For now we ignore the error, as we'll report it when + // the value is actually used. + error_code ignored_ec; + __data_.__write_time_ = + __extract_last_write_time(__p_, full_st, &ignored_ec); + } + + return failure_ec; +} +#else +error_code directory_entry::__do_refresh() noexcept { + __data_.__reset(); + error_code failure_ec; + + file_status st = _VSTD_FS::symlink_status(__p_, failure_ec); + if (!status_known(st)) { + __data_.__reset(); + return failure_ec; + } + + if (!_VSTD_FS::exists(st) || !_VSTD_FS::is_symlink(st)) { + __data_.__cache_type_ = directory_entry::_RefreshNonSymlink; + __data_.__type_ = st.type(); + __data_.__non_sym_perms_ = st.permissions(); + } else { // we have a symlink + __data_.__sym_perms_ = st.permissions(); + // Get the information about the linked entity. + // Ignore errors from stat, since we don't want errors regarding symlink + // resolution to be reported to the user. + error_code ignored_ec; + st = _VSTD_FS::status(__p_, ignored_ec); + + __data_.__type_ = st.type(); + __data_.__non_sym_perms_ = st.permissions(); + + // If we failed to resolve the link, then only partially populate the + // cache. + if (!status_known(st)) { + __data_.__cache_type_ = directory_entry::_RefreshSymlinkUnresolved; + return error_code{}; + } + // Otherwise, we resolved the link as not existing. That's OK. + __data_.__cache_type_ = directory_entry::_RefreshSymlink; + } + + // FIXME: This is currently broken, and the implementation only a placeholder. + // We need to cache last_write_time, file_size, and hard_link_count here before + // the implementation actually works. + + return failure_ec; +} +#endif _LIBCPP_END_NAMESPACE_EXPERIMENTAL_FILESYSTEM Index: test/libcxx/experimental/filesystem/class.directory_entry/directory_entry.mods/last_write_time.sh.cpp =================================================================== --- /dev/null +++ test/libcxx/experimental/filesystem/class.directory_entry/directory_entry.mods/last_write_time.sh.cpp @@ -0,0 +1,96 @@ +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++98, c++03 + +// + +// class directory_entry + +// RUN: %build -I%libcxx_src_root/src/experimental/filesystem +// RUN: %run + +#include "filesystem_include.hpp" +#include +#include + +#include "test_macros.h" +#include "rapid-cxx-test.hpp" +#include "filesystem_test_helper.hpp" + +#include "filesystem_common.h" + +using namespace fs::detail; + +TEST_SUITE(directory_entry_mods_suite) + +TEST_CASE(last_write_time_not_representable_error) { + using namespace fs; + using namespace std::chrono; + scoped_test_env env; + const path dir = env.create_dir("dir"); + const path file = env.create_file("dir/file", 42); + + TimeSpec ToTime; + ToTime.tv_sec = std::numeric_limits::max(); + ToTime.tv_nsec = duration_cast(seconds(1)).count() - 1; + + TimeStructArray TS; + SetTimeStructTo(TS[0], ToTime); + SetTimeStructTo(TS[1], ToTime); + + file_time_type old_time = last_write_time(file); + directory_entry ent(file); + + file_time_type start_time = file_time_type::clock::now() - hours(1); + last_write_time(file, start_time); + + TEST_CHECK(ent.last_write_time() == old_time); + + bool IsRepresentable = true; + file_time_type rep_value; + { + std::error_code ec; + if (SetFileTimes(file, TS, ec)) { + TEST_REQUIRE(false && "unsupported"); + } + ec.clear(); + rep_value = last_write_time(file, ec); + IsRepresentable = !bool(ec); + } + + if (!IsRepresentable) { + std::error_code rec = GetTestEC(); + ent.refresh(rec); + TEST_CHECK(!rec); + + const std::errc expected_err = std::errc::value_too_large; + + std::error_code ec = GetTestEC(); + TEST_CHECK(ent.last_write_time(ec) == file_time_type::min()); + TEST_CHECK(ErrorIs(ec, expected_err)); + + ec = GetTestEC(); + TEST_CHECK(last_write_time(file, ec) == file_time_type::min()); + TEST_CHECK(ErrorIs(ec, expected_err)); + + ExceptionChecker CheckExcept(file, expected_err); + TEST_CHECK_THROW_RESULT(fs::filesystem_error, CheckExcept, + ent.last_write_time()); + + } else { + ent.refresh(); + + std::error_code ec = GetTestEC(); + TEST_CHECK(ent.last_write_time(ec) == rep_value); + TEST_CHECK(!ec); + } +} + +TEST_SUITE_END() Index: test/libcxx/experimental/filesystem/convert_file_time.sh.cpp =================================================================== --- test/libcxx/experimental/filesystem/convert_file_time.sh.cpp +++ test/libcxx/experimental/filesystem/convert_file_time.sh.cpp @@ -23,12 +23,12 @@ #include #include -#include "filesystem_time_helper.h" +#include "filesystem_common.h" using namespace std::chrono; namespace fs = std::experimental::filesystem; using fs::file_time_type; -using fs::fs_time_util; +using fs::detail::fs_time_util; enum TestKind { TK_64Bit, TK_32Bit, TK_FloatingPoint }; Index: test/std/experimental/filesystem/class.directory_entry/directory_entry.cons.pass.cpp =================================================================== --- test/std/experimental/filesystem/class.directory_entry/directory_entry.cons.pass.cpp +++ /dev/null @@ -1,96 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// The LLVM Compiler Infrastructure -// -// This file is dual licensed under the MIT and the University of Illinois Open -// Source Licenses. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// - -// UNSUPPORTED: c++98, c++03 - -// - -// class directory_entry - -// directory_entry() noexcept = default; -// directory_entry(const directory_entry&) = default; -// directory_entry(directory_entry&&) noexcept = default; -// explicit directory_entry(const path); - -#include "filesystem_include.hpp" -#include -#include - - -void test_default_ctor() -{ - using namespace fs; - // Default - { - static_assert(std::is_nothrow_default_constructible::value, - "directory_entry must have a nothrow default constructor"); - directory_entry e; - assert(e.path() == path()); - } -} - - -void test_copy_ctor() -{ - using namespace fs; - // Copy - { - static_assert(std::is_copy_constructible::value, - "directory_entry must be copy constructible"); - static_assert(!std::is_nothrow_copy_constructible::value, - "directory_entry's copy constructor cannot be noexcept"); - const path p("foo/bar/baz"); - const directory_entry e(p); - assert(e.path() == p); - directory_entry e2(e); - assert(e.path() == p); - assert(e2.path() == p); - } - -} - -void test_move_ctor() -{ - using namespace fs; - // Move - { - static_assert(std::is_nothrow_move_constructible::value, - "directory_entry must be nothrow move constructible"); - const path p("foo/bar/baz"); - directory_entry e(p); - assert(e.path() == p); - directory_entry e2(std::move(e)); - assert(e2.path() == p); - assert(e.path() != p); // Testing moved from state. - } -} - -void test_path_ctor() { - using namespace fs; - { - static_assert(std::is_constructible::value, - "directory_entry must be constructible from path"); - static_assert(!std::is_nothrow_constructible::value, - "directory_entry constructor should not be noexcept"); - static_assert(!std::is_convertible::value, - "directory_entry constructor should be explicit"); - } - { - const path p("foo/bar/baz"); - const directory_entry e(p); - assert(p == e.path()); - } -} - -int main() { - test_default_ctor(); - test_copy_ctor(); - test_move_ctor(); - test_path_ctor(); -} Index: test/std/experimental/filesystem/class.directory_entry/directory_entry.cons/copy.pass.cpp =================================================================== --- /dev/null +++ test/std/experimental/filesystem/class.directory_entry/directory_entry.cons/copy.pass.cpp @@ -0,0 +1,74 @@ +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++98, c++03 + +// + +// class directory_entry + +// directory_entry(const directory_entry&) = default; + +#include "filesystem_include.hpp" +#include +#include + +#include "test_macros.h" +#include "rapid-cxx-test.hpp" +#include "filesystem_test_helper.hpp" +#include "test_convertible.hpp" + +TEST_SUITE(directory_entry_path_ctor_suite) + +TEST_CASE(copy_ctor) { + using namespace fs; + // Copy + { + static_assert(std::is_copy_constructible::value, + "directory_entry must be copy constructible"); + static_assert(!std::is_nothrow_copy_constructible::value, + "directory_entry's copy constructor cannot be noexcept"); + const path p("foo/bar/baz"); + const directory_entry e(p); + assert(e.path() == p); + directory_entry e2(e); + assert(e.path() == p); + assert(e2.path() == p); + } +} + +TEST_CASE(copy_ctor_copies_cache) { + using namespace fs; + scoped_test_env env; + const path dir = env.create_dir("dir"); + const path file = env.create_file("dir/file", 42); + const path sym = env.create_symlink("dir/file", "sym"); + + { + directory_entry ent(sym); + + fs::remove(sym); + + directory_entry ent_cp(ent); + TEST_CHECK(ent_cp.path() == sym); + TEST_CHECK(ent_cp.is_symlink()); + } + + { + directory_entry ent(file); + + fs::remove(file); + + directory_entry ent_cp(ent); + TEST_CHECK(ent_cp.path() == file); + TEST_CHECK(ent_cp.is_regular_file()); + } +} + +TEST_SUITE_END() Index: test/std/experimental/filesystem/class.directory_entry/directory_entry.cons/copy_assign.pass.cpp =================================================================== --- /dev/null +++ test/std/experimental/filesystem/class.directory_entry/directory_entry.cons/copy_assign.pass.cpp @@ -0,0 +1,82 @@ +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++98, c++03 + +// + +// class directory_entry + +// directory_entry& operator=(directory_entry const&) = default; +// directory_entry& operator=(directory_entry&&) noexcept = default; +// void assign(path const&); +// void replace_filename(path const&); + +#include "filesystem_include.hpp" +#include +#include + +#include "test_macros.h" +#include "rapid-cxx-test.hpp" +#include "filesystem_test_helper.hpp" + +TEST_SUITE(directory_entry_ctor_suite) + +TEST_CASE(test_copy_assign_operator) { + using namespace fs; + // Copy + { + static_assert(std::is_copy_assignable::value, + "directory_entry must be copy assignable"); + static_assert(!std::is_nothrow_copy_assignable::value, + "directory_entry's copy assignment cannot be noexcept"); + const path p("foo/bar/baz"); + const path p2("abc"); + const directory_entry e(p); + directory_entry e2; + assert(e.path() == p && e2.path() == path()); + e2 = e; + assert(e.path() == p && e2.path() == p); + directory_entry e3(p2); + e2 = e3; + assert(e2.path() == p2 && e3.path() == p2); + } +} + +TEST_CASE(copy_assign_copies_cache) { + using namespace fs; + scoped_test_env env; + const path dir = env.create_dir("dir"); + const path file = env.create_file("dir/file", 42); + const path sym = env.create_symlink("dir/file", "sym"); + + { + directory_entry ent(sym); + + fs::remove(sym); + + directory_entry ent_cp; + ent_cp = ent; + TEST_CHECK(ent_cp.path() == sym); + TEST_CHECK(ent_cp.is_symlink()); + } + + { + directory_entry ent(file); + + fs::remove(file); + + directory_entry ent_cp; + ent_cp = ent; + TEST_CHECK(ent_cp.path() == file); + TEST_CHECK(ent_cp.is_regular_file()); + } +} + +TEST_SUITE_END() Index: test/std/experimental/filesystem/class.directory_entry/directory_entry.cons/default.pass.cpp =================================================================== --- /dev/null +++ test/std/experimental/filesystem/class.directory_entry/directory_entry.cons/default.pass.cpp @@ -0,0 +1,31 @@ +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++98, c++03 + +// + +// class directory_entry + +// directory_entry() noexcept = default; + +#include "filesystem_include.hpp" +#include +#include + +int main() { + using namespace fs; + // Default + { + static_assert(std::is_nothrow_default_constructible::value, + "directory_entry must have a nothrow default constructor"); + directory_entry e; + assert(e.path() == path()); + } +} Index: test/std/experimental/filesystem/class.directory_entry/directory_entry.cons/move.pass.cpp =================================================================== --- /dev/null +++ test/std/experimental/filesystem/class.directory_entry/directory_entry.cons/move.pass.cpp @@ -0,0 +1,72 @@ +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++98, c++03 + +// + +// class directory_entry + +// directory_entry(directory_entry&&) noexcept = default; + +#include "filesystem_include.hpp" +#include +#include + +#include "test_macros.h" +#include "rapid-cxx-test.hpp" +#include "filesystem_test_helper.hpp" +#include "test_convertible.hpp" + +TEST_SUITE(directory_entry_path_ctor_suite) + +TEST_CASE(move_ctor) { + using namespace fs; + // Move + { + static_assert(std::is_nothrow_move_constructible::value, + "directory_entry must be nothrow move constructible"); + const path p("foo/bar/baz"); + directory_entry e(p); + assert(e.path() == p); + directory_entry e2(std::move(e)); + assert(e2.path() == p); + assert(e.path() != p); // Testing moved from state. + } +} + +TEST_CASE(move_ctor_copies_cache) { + using namespace fs; + scoped_test_env env; + const path dir = env.create_dir("dir"); + const path file = env.create_file("dir/file", 42); + const path sym = env.create_symlink("dir/file", "sym"); + + { + directory_entry ent(sym); + + fs::remove(sym); + + directory_entry ent_cp(std::move(ent)); + TEST_CHECK(ent_cp.path() == sym); + TEST_CHECK(ent_cp.is_symlink()); + } + + { + directory_entry ent(file); + + fs::remove(file); + + directory_entry ent_cp(std::move(ent)); + TEST_CHECK(ent_cp.path() == file); + TEST_CHECK(ent_cp.is_regular_file()); + } +} + +TEST_SUITE_END() Index: test/std/experimental/filesystem/class.directory_entry/directory_entry.cons/move_assign.pass.cpp =================================================================== --- /dev/null +++ test/std/experimental/filesystem/class.directory_entry/directory_entry.cons/move_assign.pass.cpp @@ -0,0 +1,78 @@ +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++98, c++03 + +// + +// class directory_entry + +// directory_entry& operator=(directory_entry const&) = default; +// directory_entry& operator=(directory_entry&&) noexcept = default; +// void assign(path const&); +// void replace_filename(path const&); + +#include "filesystem_include.hpp" +#include +#include + +#include "test_macros.h" +#include "rapid-cxx-test.hpp" +#include "filesystem_test_helper.hpp" + +TEST_SUITE(directory_entry_ctor_suite) + +TEST_CASE(test_move_assign_operator) { + using namespace fs; + // Copy + { + static_assert(std::is_nothrow_move_assignable::value, + "directory_entry is noexcept move assignable"); + const path p("foo/bar/baz"); + const path p2("abc"); + directory_entry e(p); + directory_entry e2(p2); + assert(e.path() == p && e2.path() == p2); + e2 = std::move(e); + assert(e2.path() == p); + assert(e.path() != p); // testing moved from state + } +} + +TEST_CASE(move_assign_copies_cache) { + using namespace fs; + scoped_test_env env; + const path dir = env.create_dir("dir"); + const path file = env.create_file("dir/file", 42); + const path sym = env.create_symlink("dir/file", "sym"); + + { + directory_entry ent(sym); + + fs::remove(sym); + + directory_entry ent_cp; + ent_cp = std::move(ent); + TEST_CHECK(ent_cp.path() == sym); + TEST_CHECK(ent_cp.is_symlink()); + } + + { + directory_entry ent(file); + + fs::remove(file); + + directory_entry ent_cp; + ent_cp = std::move(ent); + TEST_CHECK(ent_cp.path() == file); + TEST_CHECK(ent_cp.is_regular_file()); + } +} + +TEST_SUITE_END() Index: test/std/experimental/filesystem/class.directory_entry/directory_entry.cons/path.pass.cpp =================================================================== --- /dev/null +++ test/std/experimental/filesystem/class.directory_entry/directory_entry.cons/path.pass.cpp @@ -0,0 +1,182 @@ +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++98, c++03 + +// + +// class directory_entry + +// explicit directory_entry(const path); +// directory_entry(const path&, error_code& ec); + +#include "filesystem_include.hpp" +#include +#include + +#include "test_macros.h" +#include "rapid-cxx-test.hpp" +#include "filesystem_test_helper.hpp" +#include "test_convertible.hpp" + +TEST_SUITE(directory_entry_path_ctor_suite) + +TEST_CASE(path_ctor) { + using namespace fs; + { + static_assert(std::is_constructible::value, + "directory_entry must be constructible from path"); + static_assert( + !std::is_nothrow_constructible::value, + "directory_entry constructor should not be noexcept"); + static_assert(!std::is_convertible::value, + "directory_entry constructor should be explicit"); + } + { + const path p("foo/bar/baz"); + const directory_entry e(p); + TEST_CHECK(e.path() == p); + } +} + +TEST_CASE(path_ec_ctor) { + using namespace fs; + { + static_assert( + std::is_constructible::value, + "directory_entry must be constructible from path and error_code"); + static_assert(!std::is_nothrow_constructible::value, + "directory_entry constructor should not be noexcept"); + static_assert( + test_convertible(), + "directory_entry constructor should not be explicit"); + } + { + std::error_code ec = GetTestEC(); + const directory_entry e(StaticEnv::File, ec); + TEST_CHECK(e.path() == StaticEnv::File); + TEST_CHECK(!ec); + } + { + const path p("foo/bar/baz"); + std::error_code ec = GetTestEC(); + const directory_entry e(p, ec); + TEST_CHECK(e.path() == p); + TEST_CHECK(ErrorIs(ec, std::errc::no_such_file_or_directory)); + } +} + +TEST_CASE(path_ctor_calls_refresh) { + using namespace fs; + scoped_test_env env; + const path dir = env.create_dir("dir"); + const path file = env.create_file("dir/file", 42); + const path sym = env.create_symlink("dir/file", "sym"); + + { + directory_entry ent(file); + std::error_code ec = GetTestEC(); + directory_entry ent_ec(file, ec); + TEST_CHECK(!ec); + + LIBCPP_ONLY(remove(file)); + + TEST_CHECK(ent.exists()); + TEST_CHECK(ent_ec.exists()); + + TEST_CHECK(ent.file_size() == 42); + TEST_CHECK(ent_ec.file_size() == 42); + } + + env.create_file("dir/file", 101); + + { + directory_entry ent(sym); + std::error_code ec = GetTestEC(); + directory_entry ent_ec(sym, ec); + TEST_CHECK(!ec); + + LIBCPP_ONLY(remove(file)); + LIBCPP_ONLY(remove(sym)); + + TEST_CHECK(ent.is_symlink()); + TEST_CHECK(ent_ec.is_symlink()); + + TEST_CHECK(ent.is_regular_file()); + TEST_CHECK(ent_ec.is_regular_file()); + + TEST_CHECK(ent.file_size() == 101); + TEST_CHECK(ent_ec.file_size() == 101); + } +} + +TEST_CASE(path_ctor_dne) { + using namespace fs; + + { + std::error_code ec = GetTestEC(); + directory_entry ent(StaticEnv::DNE, ec); + TEST_CHECK(ErrorIs(ec, std::errc::no_such_file_or_directory)); + TEST_CHECK(ent.path() == StaticEnv::DNE); + } + // don't report dead symlinks as an error. + { + std::error_code ec = GetTestEC(); + directory_entry ent(StaticEnv::BadSymlink, ec); + TEST_CHECK(!ec); + TEST_CHECK(ent.path() == StaticEnv::BadSymlink); + } + // DNE does not cause the constructor to throw + { + directory_entry ent(StaticEnv::DNE); + TEST_CHECK(ent.path() == StaticEnv::DNE); + + directory_entry ent_two(StaticEnv::BadSymlink); + TEST_CHECK(ent_two.path() == StaticEnv::BadSymlink); + } +} + +TEST_CASE(path_ctor_cannot_resolve) { + using namespace fs; + scoped_test_env env; + const path dir = env.create_dir("dir"); + const path file = env.create_file("dir/file", 42); + const path file_out_of_dir = env.create_file("file1", 101); + const path sym_out_of_dir = env.create_symlink("dir/file", "sym"); + const path sym_in_dir = env.create_symlink("dir/file1", "dir/sym2"); + permissions(dir, perms::none); + + { + std::error_code ec = GetTestEC(); + directory_entry ent(file, ec); + TEST_CHECK(ErrorIs(ec, std::errc::permission_denied)); + TEST_CHECK(ent.path() == file); + } + { + std::error_code ec = GetTestEC(); + directory_entry ent(sym_in_dir, ec); + TEST_CHECK(ErrorIs(ec, std::errc::permission_denied)); + TEST_CHECK(ent.path() == sym_in_dir); + } + { + std::error_code ec = GetTestEC(); + directory_entry ent(sym_out_of_dir, ec); + TEST_CHECK(!ec); + TEST_CHECK(ent.path() == sym_out_of_dir); + } + { + TEST_CHECK_NO_THROW(directory_entry(file)); + TEST_CHECK_NO_THROW(directory_entry(sym_in_dir)); + TEST_CHECK_NO_THROW(directory_entry(sym_out_of_dir)); + } +} + +TEST_SUITE_END() Index: test/std/experimental/filesystem/class.directory_entry/directory_entry.mods.pass.cpp =================================================================== --- test/std/experimental/filesystem/class.directory_entry/directory_entry.mods.pass.cpp +++ /dev/null @@ -1,112 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// The LLVM Compiler Infrastructure -// -// This file is dual licensed under the MIT and the University of Illinois Open -// Source Licenses. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// - -// UNSUPPORTED: c++98, c++03 - -// - -// class directory_entry - -// directory_entry& operator=(directory_entry const&) = default; -// directory_entry& operator=(directory_entry&&) noexcept = default; -// void assign(path const&); -// void replace_filename(path const&); - -#include "filesystem_include.hpp" -#include -#include - - -void test_copy_assign_operator() -{ - using namespace fs; - // Copy - { - static_assert(std::is_copy_assignable::value, - "directory_entry must be copy assignable"); - static_assert(!std::is_nothrow_copy_assignable::value, - "directory_entry's copy assignment cannot be noexcept"); - const path p("foo/bar/baz"); - const path p2("abc"); - const directory_entry e(p); - directory_entry e2; - assert(e.path() == p && e2.path() == path()); - e2 = e; - assert(e.path() == p && e2.path() == p); - directory_entry e3(p2); - e2 = e3; - assert(e2.path() == p2 && e3.path() == p2); - } -} - - -void test_move_assign_operator() -{ - using namespace fs; - // Copy - { - static_assert(std::is_nothrow_move_assignable::value, - "directory_entry is noexcept move assignable"); - const path p("foo/bar/baz"); - const path p2("abc"); - directory_entry e(p); - directory_entry e2(p2); - assert(e.path() == p && e2.path() == p2); - e2 = std::move(e); - assert(e2.path() == p); - assert(e.path() != p); // testing moved from state - } -} - -void test_path_assign_method() -{ - using namespace fs; - const path p("foo/bar/baz"); - const path p2("abc"); - directory_entry e(p); - { - static_assert(std::is_same::value, - "return type should be void"); - static_assert(noexcept(e.assign(p)) == false, "operation must not be noexcept"); - } - { - assert(e.path() == p); - e.assign(p2); - assert(e.path() == p2 && e.path() != p); - e.assign(p); - assert(e.path() == p && e.path() != p2); - } -} - -void test_replace_filename_method() -{ - using namespace fs; - const path p("/path/to/foo.exe"); - const path replace("bar.out"); - const path expect("/path/to/bar.out"); - directory_entry e(p); - { - static_assert(noexcept(e.replace_filename(replace)) == false, - "operation cannot be noexcept"); - static_assert(std::is_same::value, - "operation must return void"); - } - { - assert(e.path() == p); - e.replace_filename(replace); - assert(e.path() == expect); - } -} - -int main() { - test_copy_assign_operator(); - test_move_assign_operator(); - test_path_assign_method(); - test_replace_filename_method(); -} Index: test/std/experimental/filesystem/class.directory_entry/directory_entry.mods/assign.pass.cpp =================================================================== --- /dev/null +++ test/std/experimental/filesystem/class.directory_entry/directory_entry.mods/assign.pass.cpp @@ -0,0 +1,132 @@ +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++98, c++03 + +// + +// class directory_entry + +// directory_entry& operator=(directory_entry const&) = default; +// directory_entry& operator=(directory_entry&&) noexcept = default; +// void assign(path const&); +// void replace_filename(path const&); + +#include "filesystem_include.hpp" +#include +#include + +#include "test_macros.h" +#include "rapid-cxx-test.hpp" +#include "filesystem_test_helper.hpp" + +TEST_SUITE(directory_entry_mods_suite) + +TEST_CASE(test_path_assign_method) { + using namespace fs; + const path p("foo/bar/baz"); + const path p2("abc"); + directory_entry e(p); + { + static_assert(std::is_same::value, + "return type should be void"); + static_assert(noexcept(e.assign(p)) == false, + "operation must not be noexcept"); + } + { + TEST_CHECK(e.path() == p); + e.assign(p2); + TEST_CHECK(e.path() == p2 && e.path() != p); + e.assign(p); + TEST_CHECK(e.path() == p && e.path() != p2); + } +} + +TEST_CASE(test_path_assign_ec_method) { + using namespace fs; + const path p("foo/bar/baz"); + const path p2("abc"); + { + std::error_code ec; + directory_entry e(p); + static_assert(std::is_same::value, + "return type should be void"); + static_assert(noexcept(e.assign(p, ec)) == false, + "operation must not be noexcept"); + } + { + directory_entry ent(p); + std::error_code ec = GetTestEC(); + ent.assign(p2, ec); + TEST_CHECK(ErrorIs(ec, std::errc::no_such_file_or_directory)); + TEST_CHECK(ent.path() == p2); + } +} + +TEST_CASE(test_assign_calls_refresh) { + using namespace fs; + scoped_test_env env; + const path dir = env.create_dir("dir"); + const path file = env.create_file("dir/file", 42); + const path sym = env.create_symlink("dir/file", "sym"); + + { + directory_entry ent; + ent.assign(file); + + // removing the file demonstrates that the values where cached previously. + LIBCPP_ONLY(remove(file)); + + TEST_CHECK(ent.is_regular_file()); + } + env.create_file("dir/file", 101); + { + directory_entry ent; + ent.assign(sym); + + LIBCPP_ONLY(remove(file)); + LIBCPP_ONLY(remove(sym)); + + TEST_CHECK(ent.is_symlink()); + TEST_CHECK(ent.is_regular_file()); + } +} + +TEST_CASE(test_assign_propagates_error) { + using namespace fs; + scoped_test_env env; + const path dir = env.create_dir("dir"); + const path file = env.create_file("dir/file", 42); + const path sym_out_of_dir = env.create_symlink("dir/file", "sym"); + const path file_out_of_dir = env.create_file("file1"); + const path sym_in_dir = env.create_symlink("file1", "dir/sym1"); + + permissions(dir, perms::none); + + { + directory_entry ent; + std::error_code ec = GetTestEC(); + ent.assign(file, ec); + TEST_CHECK(ErrorIs(ec, std::errc::permission_denied)); + } + { + directory_entry ent; + std::error_code ec = GetTestEC(); + ent.assign(sym_in_dir, ec); + TEST_CHECK(ErrorIs(ec, std::errc::permission_denied)); + } + { + directory_entry ent; + std::error_code ec = GetTestEC(); + ent.assign(sym_out_of_dir, ec); + TEST_CHECK(!ec); + } +} + +TEST_SUITE_END() Index: test/std/experimental/filesystem/class.directory_entry/directory_entry.mods/refresh.pass.cpp =================================================================== --- /dev/null +++ test/std/experimental/filesystem/class.directory_entry/directory_entry.mods/refresh.pass.cpp @@ -0,0 +1,339 @@ +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++98, c++03 + +// + +// class directory_entry + +// directory_entry& operator=(directory_entry const&) = default; +// directory_entry& operator=(directory_entry&&) noexcept = default; +// void assign(path const&); +// void replace_filename(path const&); + +#include "filesystem_include.hpp" +#include +#include + +#include "test_macros.h" +#include "rapid-cxx-test.hpp" +#include "filesystem_test_helper.hpp" + +TEST_SUITE(directory_entry_mods_suite) + +TEST_CASE(test_refresh_method) { + using namespace fs; + { + directory_entry e; + static_assert(noexcept(e.refresh()) == false, + "operation cannot be noexcept"); + static_assert(std::is_same::value, + "operation must return void"); + } + { + directory_entry e; + e.refresh(); + TEST_CHECK(!e.exists()); + } +} + +TEST_CASE(test_refresh_ec_method) { + using namespace fs; + { + directory_entry e; + std::error_code ec; + static_assert(noexcept(e.refresh(ec)), "operation should be noexcept"); + static_assert(std::is_same::value, + "operation must return void"); + } + { + directory_entry e; + std::error_code ec = GetTestEC(); + e.refresh(ec); + TEST_CHECK(ErrorIs(ec, std::errc::no_such_file_or_directory)); + } +} + +TEST_CASE(refresh_on_file_dne) { + using namespace fs; + scoped_test_env env; + const path dir = env.create_dir("dir"); + const path file = env.create_file("dir/file", 42); + + const perms old_perms = status(dir).permissions(); + + // test file doesn't exist + { + directory_entry ent(file); + remove(file); + TEST_CHECK(ent.exists()); + + ent.refresh(); + + permissions(dir, perms::none); + TEST_CHECK(!ent.exists()); + } + permissions(dir, old_perms); + env.create_file("dir/file", 101); + { + directory_entry ent(file); + remove(file); + TEST_CHECK(ent.exists()); + + std::error_code ec = GetTestEC(); + ent.refresh(ec); + TEST_CHECK(ErrorIs(ec, std::errc::no_such_file_or_directory)); + + permissions(dir, perms::none); + TEST_CHECK(!ent.exists()); + } +} + +void remove_if_exists(const fs::path& p) { + std::error_code ec; + remove(p, ec); +} + +TEST_CASE(refresh_on_bad_symlink) { + using namespace fs; + scoped_test_env env; + const path dir = env.create_dir("dir"); + const path file = env.create_file("dir/file", 42); + const path sym = env.create_symlink("dir/file", "sym"); + + const perms old_perms = status(dir).permissions(); + + // test file doesn't exist + { + directory_entry ent(sym); + LIBCPP_ONLY(remove(file)); + TEST_CHECK(ent.is_symlink()); + TEST_CHECK(ent.is_regular_file()); + TEST_CHECK(ent.exists()); + + remove_if_exists(file); + ent.refresh(); + + LIBCPP_ONLY(permissions(dir, perms::none)); + TEST_CHECK(ent.is_symlink()); + TEST_CHECK(!ent.is_regular_file()); + TEST_CHECK(!ent.exists()); + } + permissions(dir, old_perms); + env.create_file("dir/file", 101); + { + directory_entry ent(sym); + LIBCPP_ONLY(remove(file)); + TEST_CHECK(ent.is_symlink()); + TEST_CHECK(ent.is_regular_file()); + TEST_CHECK(ent.exists()); + + remove_if_exists(file); + + std::error_code ec = GetTestEC(); + ent.refresh(ec); + TEST_CHECK(!ec); // we don't report bad symlinks as an error. + + LIBCPP_ONLY(permissions(dir, perms::none)); + TEST_CHECK(!ent.exists()); + } +} + +TEST_CASE(refresh_cannot_resolve) { + using namespace fs; + scoped_test_env env; + const path dir = env.create_dir("dir"); + const path file = env.create_file("dir/file", 42); + const path file_out_of_dir = env.create_file("file1", 99); + const path sym_out_of_dir = env.create_symlink("dir/file", "sym"); + const path sym_in_dir = env.create_symlink("file1", "dir/sym1"); + perms old_perms = status(dir).permissions(); + + { + directory_entry ent(file); + permissions(dir, perms::none); + + TEST_CHECK(ent.is_regular_file()); + + std::error_code ec = GetTestEC(); + ent.refresh(ec); + + TEST_CHECK(ErrorIs(ec, std::errc::permission_denied)); + TEST_CHECK(ent.path() == file); + + ExceptionChecker Checker(file, std::errc::permission_denied); + TEST_CHECK_THROW_RESULT(filesystem_error, Checker, ent.refresh()); + } + permissions(dir, old_perms); + { + directory_entry ent(sym_in_dir); + permissions(dir, perms::none); + TEST_CHECK(ent.is_symlink()); + + std::error_code ec = GetTestEC(); + ent.refresh(ec); + TEST_CHECK(ErrorIs(ec, std::errc::permission_denied)); + TEST_CHECK(ent.path() == sym_in_dir); + + ExceptionChecker Checker(sym_in_dir, std::errc::permission_denied); + TEST_CHECK_THROW_RESULT(filesystem_error, Checker, ent.refresh()); + } + permissions(dir, old_perms); + { + directory_entry ent(sym_out_of_dir); + permissions(dir, perms::none); + TEST_CHECK(ent.is_symlink()); + + // Failure to resolve the linked entity due to permissions is not + // reported as an error. + std::error_code ec = GetTestEC(); + ent.refresh(ec); + TEST_CHECK(!ec); + TEST_CHECK(ent.is_symlink()); + + ec = GetTestEC(); + TEST_CHECK(ent.exists(ec) == false); + TEST_CHECK(ErrorIs(ec, std::errc::permission_denied)); + TEST_CHECK(ent.path() == sym_out_of_dir); + } + permissions(dir, old_perms); + { + directory_entry ent_file(file); + directory_entry ent_sym(sym_in_dir); + directory_entry ent_sym2(sym_out_of_dir); + permissions(dir, perms::none); + ((void)ent_file); + ((void)ent_sym); + + TEST_CHECK_THROW(filesystem_error, ent_file.refresh()); + TEST_CHECK_THROW(filesystem_error, ent_sym.refresh()); + TEST_CHECK_NO_THROW(ent_sym2); + } +} + +TEST_CASE(refresh_doesnt_throw_on_dne_but_reports_it) { + using namespace fs; + scoped_test_env env; + + const path file = env.create_file("file1", 42); + const path sym = env.create_symlink("file1", "sym"); + + { + directory_entry ent(file); + TEST_CHECK(ent.file_size() == 42); + + remove(file); + + std::error_code ec = GetTestEC(); + ent.refresh(ec); + TEST_CHECK(ErrorIs(ec, std::errc::no_such_file_or_directory)); + TEST_CHECK_NO_THROW(ent.refresh()); + + ec = GetTestEC(); + TEST_CHECK(ent.file_size(ec) == uintmax_t(-1)); + TEST_CHECK(ErrorIs(ec, std::errc::no_such_file_or_directory)); + + // doesn't throw! + TEST_CHECK_THROW(filesystem_error, ent.file_size()); + } + env.create_file("file1", 99); + { + directory_entry ent(sym); + TEST_CHECK(ent.is_symlink()); + TEST_CHECK(ent.is_regular_file()); + TEST_CHECK(ent.file_size() == 99); + + remove(file); + + std::error_code ec = GetTestEC(); + ent.refresh(ec); + TEST_CHECK(!ec); + + ec = GetTestEC(); + TEST_CHECK(ent.file_size(ec) == uintmax_t(-1)); + TEST_CHECK(ErrorIs(ec, std::errc::no_such_file_or_directory)); + + TEST_CHECK_THROW(filesystem_error, ent.file_size()); + } +} + +TEST_CASE(access_cache_after_refresh_fails) { + using namespace fs; + scoped_test_env env; + const path dir = env.create_dir("dir"); + const path file = env.create_file("dir/file", 42); + const path file_out_of_dir = env.create_file("file1", 101); + const path sym = env.create_symlink("dir/file", "sym"); + const path sym_in_dir = env.create_symlink("dir/file", "dir/sym2"); + + const perms old_perms = status(dir).permissions(); + +#define CHECK_ACCESS(func, expect) \ + ec = GetTestEC(); \ + TEST_CHECK(ent.func(ec) == expect); \ + TEST_CHECK(ErrorIs(ec, std::errc::permission_denied)) + + // test file doesn't exist + { + directory_entry ent(file); + + TEST_CHECK(!ent.is_symlink()); + TEST_CHECK(ent.is_regular_file()); + TEST_CHECK(ent.exists()); + + permissions(dir, perms::none); + std::error_code ec = GetTestEC(); + ent.refresh(ec); + TEST_CHECK(ErrorIs(ec, std::errc::permission_denied)); + + CHECK_ACCESS(exists, false); + CHECK_ACCESS(is_symlink, false); + CHECK_ACCESS(last_write_time, file_time_type::min()); + CHECK_ACCESS(hard_link_count, uintmax_t(-1)); + } + permissions(dir, old_perms); + { + directory_entry ent(sym_in_dir); + TEST_CHECK(ent.is_symlink()); + TEST_CHECK(ent.is_regular_file()); + TEST_CHECK(ent.exists()); + + permissions(dir, perms::none); + std::error_code ec = GetTestEC(); + ent.refresh(ec); + TEST_CHECK(ErrorIs(ec, std::errc::permission_denied)); + + CHECK_ACCESS(exists, false); + CHECK_ACCESS(is_symlink, false); + CHECK_ACCESS(last_write_time, file_time_type::min()); + CHECK_ACCESS(hard_link_count, uintmax_t(-1)); + } + permissions(dir, old_perms); + { + directory_entry ent(sym); + TEST_CHECK(ent.is_symlink()); + TEST_CHECK(ent.is_regular_file()); + TEST_CHECK(ent.exists()); + + permissions(dir, perms::none); + std::error_code ec = GetTestEC(); + ent.refresh(ec); + TEST_CHECK(!ec); + TEST_CHECK(ent.is_symlink()); + + CHECK_ACCESS(exists, false); + CHECK_ACCESS(is_regular_file, false); + CHECK_ACCESS(last_write_time, file_time_type::min()); + CHECK_ACCESS(hard_link_count, uintmax_t(-1)); + } +#undef CHECK_ACCESS +} + +TEST_SUITE_END() Index: test/std/experimental/filesystem/class.directory_entry/directory_entry.mods/replace_filename.pass.cpp =================================================================== --- /dev/null +++ test/std/experimental/filesystem/class.directory_entry/directory_entry.mods/replace_filename.pass.cpp @@ -0,0 +1,169 @@ +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++98, c++03 + +// + +// class directory_entry + +// directory_entry& operator=(directory_entry const&) = default; +// directory_entry& operator=(directory_entry&&) noexcept = default; +// void assign(path const&); +// void replace_filename(path const&); + +#include "filesystem_include.hpp" +#include +#include + +#include "test_macros.h" +#include "rapid-cxx-test.hpp" +#include "filesystem_test_helper.hpp" + +TEST_SUITE(directory_entry_mods_suite) + +TEST_CASE(test_replace_filename_method) { + using namespace fs; + + { + directory_entry e; + path replace; + static_assert(noexcept(e.replace_filename(replace)) == false, + "operation cannot be noexcept"); + static_assert( + std::is_same::value, + "operation must return void"); + } + { + const path p("/path/to/foo.exe"); + const path replace("bar.out"); + const path expect("/path/to/bar.out"); + directory_entry e(p); + TEST_CHECK(e.path() == p); + e.replace_filename(replace); + TEST_CHECK(e.path() == expect); + } +} + +TEST_CASE(test_replace_filename_ec_method) { + using namespace fs; + + { + directory_entry e; + path replace; + std::error_code ec; + static_assert(noexcept(e.replace_filename(replace, ec)) == false, + "operation cannot be noexcept"); + static_assert( + std::is_same::value, + "operation must return void"); + } + { + const path p("/path/to/foo.exe"); + const path replace("bar.out"); + const path expect("/path/to/bar.out"); + directory_entry e(p); + TEST_CHECK(e.path() == p); + std::error_code ec = GetTestEC(); + e.replace_filename(replace, ec); + TEST_CHECK(e.path() == expect); + TEST_CHECK(ErrorIs(ec, std::errc::no_such_file_or_directory)); + } + { + const path p = StaticEnv::EmptyFile; + const path expect = StaticEnv::NonEmptyFile; + const path replace = StaticEnv::NonEmptyFile.filename(); + TEST_REQUIRE(expect.parent_path() == p.parent_path()); + directory_entry e(p); + TEST_CHECK(e.path() == p); + std::error_code ec = GetTestEC(); + e.replace_filename(replace, ec); + TEST_CHECK(e.path() == expect); + TEST_CHECK(!ec); + } +} + +TEST_CASE(test_replace_filename_calls_refresh) { + using namespace fs; + scoped_test_env env; + const path dir = env.create_dir("dir"); + const path file = env.create_file("dir/file", 42); + const path file_two = env.create_file("dir/file_two", 101); + const path sym = env.create_symlink("dir/file", "sym"); + const path sym_two = env.create_symlink("dir/file_two", "sym_two"); + + { + directory_entry ent(file); + ent.replace_filename(file_two.filename()); + TEST_REQUIRE(ent.path() == file_two); + + // removing the file demonstrates that the values where cached previously. + LIBCPP_ONLY(remove(file_two)); + + TEST_CHECK(ent.file_size() == 101); + } + env.create_file("dir/file_two", 99); + { + directory_entry ent(sym); + ent.replace_filename(sym_two.filename()); + TEST_REQUIRE(ent.path() == sym_two); + + LIBCPP_ONLY(remove(file_two)); + LIBCPP_ONLY(remove(sym_two)); + + TEST_CHECK(ent.is_symlink()); + TEST_CHECK(ent.is_regular_file()); + TEST_CHECK(ent.file_size() == 99); + } +} + +TEST_CASE(test_replace_filename_propagates_error) { + using namespace fs; + scoped_test_env env; + const path dir = env.create_dir("dir"); + const path file = env.create_file("dir/file", 42); + const path file_two = env.create_file("dir/file_two", 99); + const path file_out_of_dir = env.create_file("file_three", 101); + const path sym_out_of_dir = env.create_symlink("dir/file", "sym"); + const path sym_out_of_dir_two = env.create_symlink("dir/file", "sym_two"); + const path sym_in_dir = env.create_symlink("file_two", "dir/sym_three"); + const path sym_in_dir_two = env.create_symlink("file_two", "dir/sym_four"); + + const perms old_perms = status(dir).permissions(); + + { + directory_entry ent(file); + permissions(dir, perms::none); + std::error_code ec = GetTestEC(); + ent.replace_filename(file_two.filename(), ec); + TEST_CHECK(ErrorIs(ec, std::errc::permission_denied)); + } + permissions(dir, old_perms); + { + directory_entry ent(sym_in_dir); + permissions(dir, perms::none); + std::error_code ec = GetTestEC(); + ent.replace_filename(sym_in_dir_two.filename(), ec); + TEST_CHECK(ErrorIs(ec, std::errc::permission_denied)); + } + permissions(dir, old_perms); + { + directory_entry ent(sym_out_of_dir); + permissions(dir, perms::none); + std::error_code ec = GetTestEC(); + ent.replace_filename(sym_out_of_dir_two.filename(), ec); + TEST_CHECK(!ec); + TEST_CHECK(ent.is_symlink()); + ec = GetTestEC(); + TEST_CHECK(!ent.exists(ec)); + TEST_CHECK(ErrorIs(ec, std::errc::permission_denied)); + } +} + +TEST_SUITE_END() Index: test/std/experimental/filesystem/class.directory_entry/directory_entry.obs/file_size.pass.cpp =================================================================== --- /dev/null +++ test/std/experimental/filesystem/class.directory_entry/directory_entry.obs/file_size.pass.cpp @@ -0,0 +1,238 @@ +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++98, c++03 + +// + +// class directory_entry + +// uintmax_t file_size() const; +// uintmax_t file_size(error_code const&) const noexcept; + +#include "filesystem_include.hpp" +#include +#include + +#include "filesystem_test_helper.hpp" +#include "rapid-cxx-test.hpp" + +#include + +TEST_SUITE(directory_entry_obs_testsuite) + +TEST_CASE(signatures) { + using namespace fs; + { + const fs::directory_entry e; + std::error_code ec; + static_assert(std::is_same::value, ""); + static_assert(std::is_same::value, + ""); + static_assert(noexcept(e.file_size()) == false, ""); + static_assert(noexcept(e.file_size(ec)) == true, ""); + } +} + +TEST_CASE(basic) { + using namespace fs; + + scoped_test_env env; + const path file = env.create_file("file", 42); + const path dir = env.create_dir("dir"); + const path sym = env.create_symlink("file", "sym"); + + { + directory_entry ent(file); + uintmax_t expect = file_size(ent); + TEST_CHECK(expect == 42); + + // Remove the file to show that the results were already in the cache. + LIBCPP_ONLY(remove(file)); + + std::error_code ec = GetTestEC(); + TEST_CHECK(ent.file_size(ec) == expect); + TEST_CHECK(!ec); + } + env.create_file("file", 99); + { + directory_entry ent(sym); + + uintmax_t expect = file_size(ent); + TEST_CHECK(expect == 99); + + LIBCPP_ONLY(remove(ent)); + + std::error_code ec = GetTestEC(); + TEST_CHECK(ent.file_size(ec) == 99); + TEST_CHECK(!ec); + } +} + +TEST_CASE(not_regular_file) { + using namespace fs; + + scoped_test_env env; + struct { + const path p; + std::errc expected_err; + } TestCases[] = { + {env.create_dir("dir"), std::errc::is_a_directory}, + {env.create_fifo("fifo"), std::errc::not_supported}, + {env.create_symlink("dir", "sym"), std::errc::is_a_directory}}; + + for (auto const& TC : TestCases) { + const path& p = TC.p; + directory_entry ent(p); + TEST_CHECK(ent.path() == p); + std::error_code ec = GetTestEC(0); + + std::error_code other_ec = GetTestEC(1); + uintmax_t expect = file_size(p, other_ec); + + uintmax_t got = ent.file_size(ec); + TEST_CHECK(got == expect); + TEST_CHECK(got == uintmax_t(-1)); + TEST_CHECK(ec == other_ec); + TEST_CHECK(ErrorIs(ec, TC.expected_err)); + + ExceptionChecker Checker(p, TC.expected_err); + TEST_CHECK_THROW_RESULT(filesystem_error, Checker, ent.file_size()); + } +} + +TEST_CASE(error_reporting) { + using namespace fs; + + scoped_test_env env; + + const path dir = env.create_dir("dir"); + const path file = env.create_file("dir/file", 42); + const path file_out_of_dir = env.create_file("file2", 101); + const path sym_out_of_dir = env.create_symlink("dir/file", "sym"); + const path sym_in_dir = env.create_symlink("file2", "dir/sym2"); + + const perms old_perms = status(dir).permissions(); + + // test a file which doesn't exist + { + directory_entry ent; + + std::error_code ec = GetTestEC(); + ent.assign(StaticEnv::DNE, ec); + TEST_REQUIRE(ent.path() == StaticEnv::DNE); + TEST_CHECK(ErrorIs(ec, std::errc::no_such_file_or_directory)); + + ec = GetTestEC(); + TEST_CHECK(ent.file_size(ec) == uintmax_t(-1)); + TEST_CHECK(ErrorIs(ec, std::errc::no_such_file_or_directory)); + + ExceptionChecker Checker(StaticEnv::DNE, + std::errc::no_such_file_or_directory); + TEST_CHECK_THROW_RESULT(filesystem_error, Checker, ent.file_size()); + } + // test a dead symlink + { + directory_entry ent; + + std::error_code ec = GetTestEC(); + uintmax_t expect_bad = file_size(StaticEnv::BadSymlink, ec); + TEST_CHECK(expect_bad == uintmax_t(-1)); + TEST_CHECK(ErrorIs(ec, std::errc::no_such_file_or_directory)); + + ec = GetTestEC(); + ent.assign(StaticEnv::BadSymlink, ec); + TEST_REQUIRE(ent.path() == StaticEnv::BadSymlink); + TEST_CHECK(!ec); + + ec = GetTestEC(); + TEST_CHECK(ent.file_size(ec) == expect_bad); + TEST_CHECK(ErrorIs(ec, std::errc::no_such_file_or_directory)); + + ExceptionChecker Checker(StaticEnv::BadSymlink, + std::errc::no_such_file_or_directory); + TEST_CHECK_THROW_RESULT(filesystem_error, Checker, ent.file_size()); + } + // test a file w/o appropriate permissions. + { + directory_entry ent; + uintmax_t expect_good = file_size(file); + permissions(dir, perms::none); + + std::error_code ec = GetTestEC(); + ent.assign(file, ec); + TEST_REQUIRE(ent.path() == file); + TEST_CHECK(ErrorIs(ec, std::errc::permission_denied)); + + ec = GetTestEC(); + TEST_CHECK(ent.file_size(ec) == uintmax_t(-1)); + TEST_CHECK(ErrorIs(ec, std::errc::permission_denied)); + + ExceptionChecker Checker(file, std::errc::permission_denied); + TEST_CHECK_THROW_RESULT(filesystem_error, Checker, ent.file_size()); + + permissions(dir, old_perms); + ec = GetTestEC(); + TEST_CHECK(ent.file_size(ec) == expect_good); + TEST_CHECK(!ec); + TEST_CHECK_NO_THROW(ent.file_size()); + } + permissions(dir, old_perms); + // test a symlink w/o appropriate permissions. + { + directory_entry ent; + uintmax_t expect_good = file_size(sym_in_dir); + permissions(dir, perms::none); + + std::error_code ec = GetTestEC(); + ent.assign(sym_in_dir, ec); + TEST_REQUIRE(ent.path() == sym_in_dir); + TEST_CHECK(ErrorIs(ec, std::errc::permission_denied)); + + ec = GetTestEC(); + TEST_CHECK(ent.file_size(ec) == uintmax_t(-1)); + TEST_CHECK(ErrorIs(ec, std::errc::permission_denied)); + + ExceptionChecker Checker(sym_in_dir, std::errc::permission_denied); + TEST_CHECK_THROW_RESULT(filesystem_error, Checker, ent.file_size()); + + permissions(dir, old_perms); + ec = GetTestEC(); + TEST_CHECK(ent.file_size(ec) == expect_good); + TEST_CHECK(!ec); + TEST_CHECK_NO_THROW(ent.file_size()); + } + permissions(dir, old_perms); + // test a symlink to a file w/o appropriate permissions + { + directory_entry ent; + uintmax_t expect_good = file_size(sym_out_of_dir); + permissions(dir, perms::none); + + std::error_code ec = GetTestEC(); + ent.assign(sym_out_of_dir, ec); + TEST_REQUIRE(ent.path() == sym_out_of_dir); + TEST_CHECK(!ec); + + ec = GetTestEC(); + TEST_CHECK(ent.file_size(ec) == uintmax_t(-1)); + TEST_CHECK(ErrorIs(ec, std::errc::permission_denied)); + + ExceptionChecker Checker(sym_out_of_dir, std::errc::permission_denied); + TEST_CHECK_THROW_RESULT(filesystem_error, Checker, ent.file_size()); + + permissions(dir, old_perms); + ec = GetTestEC(); + TEST_CHECK(ent.file_size(ec) == expect_good); + TEST_CHECK(!ec); + TEST_CHECK_NO_THROW(ent.file_size()); + } +} + +TEST_SUITE_END() Index: test/std/experimental/filesystem/class.directory_entry/directory_entry.obs/file_type_obs.pass.cpp =================================================================== --- /dev/null +++ test/std/experimental/filesystem/class.directory_entry/directory_entry.obs/file_type_obs.pass.cpp @@ -0,0 +1,258 @@ +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++98, c++03 + +// + +// class directory_entry + +// file_status status() const; +// file_status status(error_code const&) const noexcept; + +#include "filesystem_include.hpp" +#include +#include + +#include "filesystem_test_helper.hpp" +#include "rapid-cxx-test.hpp" + +TEST_SUITE(directory_entry_obs_testsuite) + +TEST_CASE(file_dne) { + using namespace fs; + directory_entry p("dne"); +} + +TEST_CASE(signatures) { + using namespace fs; + const directory_entry e; + std::error_code ec; +#define TEST_FUNC(name) \ + static_assert(std::is_same::value, \ + "wrong return type"); \ + static_assert(noexcept(e.name()) == false, "should not be noexcept"); \ + static_assert(std::is_same::value, \ + "wrong return type"); \ + static_assert(noexcept(e.name(ec)) == true, "should be noexcept") + + TEST_FUNC(exists); + TEST_FUNC(is_block_file); + TEST_FUNC(is_character_file); + TEST_FUNC(is_directory); + TEST_FUNC(is_fifo); + TEST_FUNC(is_other); + TEST_FUNC(is_regular_file); + TEST_FUNC(is_socket); + TEST_FUNC(is_symlink); + +#undef TEST_FUNC +} + +TEST_CASE(test_without_ec) { + using namespace fs; + using fs::directory_entry; + using fs::file_status; + using fs::path; + + scoped_test_env env; + path f = env.create_file("foo", 42); + path d = env.create_dir("dir"); + path fifo = env.create_fifo("fifo"); + path hl = env.create_hardlink("foo", "hl"); + for (auto p : {hl, f, d, fifo}) { + directory_entry e(p); + file_status st = status(p); + file_status sym_st = symlink_status(p); + fs::remove(p); + TEST_REQUIRE(e.exists()); + TEST_REQUIRE(!exists(p)); + TEST_CHECK(e.exists() == exists(st)); + TEST_CHECK(e.is_block_file() == is_block_file(st)); + TEST_CHECK(e.is_character_file() == is_character_file(st)); + TEST_CHECK(e.is_directory() == is_directory(st)); + TEST_CHECK(e.is_fifo() == is_fifo(st)); + TEST_CHECK(e.is_other() == is_other(st)); + TEST_CHECK(e.is_regular_file() == is_regular_file(st)); + TEST_CHECK(e.is_socket() == is_socket(st)); + TEST_CHECK(e.is_symlink() == is_symlink(sym_st)); + } +} + +TEST_CASE(test_with_ec) { + using namespace fs; + using fs::directory_entry; + using fs::file_status; + using fs::path; + + scoped_test_env env; + path f = env.create_file("foo", 42); + path d = env.create_dir("dir"); + path fifo = env.create_fifo("fifo"); + path hl = env.create_hardlink("foo", "hl"); + for (auto p : {hl, f, d, fifo}) { + directory_entry e(p); + std::error_code status_ec = GetTestEC(); + std::error_code sym_status_ec = GetTestEC(1); + file_status st = status(p, status_ec); + file_status sym_st = symlink_status(p, sym_status_ec); + fs::remove(p); + std::error_code ec = GetTestEC(2); + auto CheckEC = [&](std::error_code const& other_ec) { + bool res = ec == other_ec; + ec = GetTestEC(2); + return res; + }; + + TEST_REQUIRE(e.exists(ec)); + TEST_CHECK(CheckEC(status_ec)); + TEST_REQUIRE(!exists(p)); + + TEST_CHECK(e.exists(ec) == exists(st)); + TEST_CHECK(CheckEC(status_ec)); + + TEST_CHECK(e.is_block_file(ec) == is_block_file(st)); + TEST_CHECK(CheckEC(status_ec)); + + TEST_CHECK(e.is_character_file(ec) == is_character_file(st)); + TEST_CHECK(CheckEC(status_ec)); + + TEST_CHECK(e.is_directory(ec) == is_directory(st)); + TEST_CHECK(CheckEC(status_ec)); + + TEST_CHECK(e.is_fifo(ec) == is_fifo(st)); + TEST_CHECK(CheckEC(status_ec)); + + TEST_CHECK(e.is_other(ec) == is_other(st)); + TEST_CHECK(CheckEC(status_ec)); + + TEST_CHECK(e.is_regular_file(ec) == is_regular_file(st)); + TEST_CHECK(CheckEC(status_ec)); + + TEST_CHECK(e.is_socket(ec) == is_socket(st)); + TEST_CHECK(CheckEC(status_ec)); + + TEST_CHECK(e.is_symlink(ec) == is_symlink(sym_st)); + TEST_CHECK(CheckEC(sym_status_ec)); + } +} + +TEST_CASE(test_with_ec_dne) { + using namespace fs; + using fs::directory_entry; + using fs::file_status; + using fs::path; + + for (auto p : {StaticEnv::DNE, StaticEnv::BadSymlink}) { + + directory_entry e(p); + std::error_code status_ec = GetTestEC(); + std::error_code sym_status_ec = GetTestEC(1); + file_status st = status(p, status_ec); + file_status sym_st = symlink_status(p, sym_status_ec); + std::error_code ec = GetTestEC(2); + auto CheckEC = [&](std::error_code const& other_ec) { + bool res = ec == other_ec; + ec = GetTestEC(2); + return res; + }; + + TEST_CHECK(e.exists(ec) == exists(st)); + TEST_CHECK(CheckEC(status_ec)); + + TEST_CHECK(e.is_block_file(ec) == is_block_file(st)); + TEST_CHECK(CheckEC(status_ec)); + + TEST_CHECK(e.is_character_file(ec) == is_character_file(st)); + TEST_CHECK(CheckEC(status_ec)); + + TEST_CHECK(e.is_directory(ec) == is_directory(st)); + TEST_CHECK(CheckEC(status_ec)); + + TEST_CHECK(e.is_fifo(ec) == is_fifo(st)); + TEST_CHECK(CheckEC(status_ec)); + + TEST_CHECK(e.is_other(ec) == is_other(st)); + TEST_CHECK(CheckEC(status_ec)); + + TEST_CHECK(e.is_regular_file(ec) == is_regular_file(st)); + TEST_CHECK(CheckEC(status_ec)); + + TEST_CHECK(e.is_socket(ec) == is_socket(st)); + TEST_CHECK(CheckEC(status_ec)); + + TEST_CHECK(e.is_symlink(ec) == is_symlink(sym_st)); + TEST_CHECK(CheckEC(sym_status_ec)); + } +} + +TEST_CASE(test_with_ec_cannot_resolve) { + using namespace fs; + using fs::directory_entry; + using fs::file_status; + using fs::path; + + scoped_test_env env; + const path dir = env.create_dir("dir"); + const path file = env.create_file("dir/file", 42); + const path file_out_of_dir = env.create_file("file2", 99); + const path sym = env.create_symlink("file2", "dir/sym"); + + perms old_perms = fs::status(dir).permissions(); + + for (auto p : {file, sym}) { + permissions(dir, old_perms); + directory_entry e(p); + + permissions(dir, perms::none); + std::error_code dummy_ec; + e.refresh(dummy_ec); + TEST_REQUIRE(dummy_ec); + + std::error_code status_ec = GetTestEC(); + std::error_code sym_status_ec = GetTestEC(1); + file_status st = status(p, status_ec); + file_status sym_st = symlink_status(p, sym_status_ec); + std::error_code ec = GetTestEC(2); + auto CheckEC = [&](std::error_code const& other_ec) { + bool res = ec == other_ec; + ec = GetTestEC(2); + return res; + }; + + TEST_CHECK(e.exists(ec) == exists(st)); + TEST_CHECK(CheckEC(status_ec)); + + TEST_CHECK(e.is_block_file(ec) == is_block_file(st)); + TEST_CHECK(CheckEC(status_ec)); + + TEST_CHECK(e.is_character_file(ec) == is_character_file(st)); + TEST_CHECK(CheckEC(status_ec)); + + TEST_CHECK(e.is_directory(ec) == is_directory(st)); + TEST_CHECK(CheckEC(status_ec)); + + TEST_CHECK(e.is_fifo(ec) == is_fifo(st)); + TEST_CHECK(CheckEC(status_ec)); + + TEST_CHECK(e.is_other(ec) == is_other(st)); + TEST_CHECK(CheckEC(status_ec)); + + TEST_CHECK(e.is_regular_file(ec) == is_regular_file(st)); + TEST_CHECK(CheckEC(status_ec)); + + TEST_CHECK(e.is_socket(ec) == is_socket(st)); + TEST_CHECK(CheckEC(status_ec)); + + TEST_CHECK(e.is_symlink(ec) == is_symlink(sym_st)); + TEST_CHECK(CheckEC(sym_status_ec)); + } +} + +TEST_SUITE_END() Index: test/std/experimental/filesystem/class.directory_entry/directory_entry.obs/hard_link_count.pass.cpp =================================================================== --- /dev/null +++ test/std/experimental/filesystem/class.directory_entry/directory_entry.obs/hard_link_count.pass.cpp @@ -0,0 +1,237 @@ +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++98, c++03 + +// + +// class directory_entry + +// uintmax_t hard_link_count() const; +// uintmax_t hard_link_count(error_code const&) const noexcept; + +#include "filesystem_include.hpp" +#include +#include + +#include "filesystem_test_helper.hpp" +#include "rapid-cxx-test.hpp" + +TEST_SUITE(directory_entry_obs_testsuite) + +TEST_CASE(signatures) { + using namespace fs; + { + const fs::directory_entry e; + std::error_code ec; + static_assert(std::is_same::value, ""); + static_assert(std::is_same::value, + ""); + static_assert(noexcept(e.hard_link_count()) == false, ""); + static_assert(noexcept(e.hard_link_count(ec)) == true, ""); + } +} + +TEST_CASE(basic) { + using namespace fs; + + scoped_test_env env; + const path file = env.create_file("file", 42); + const path dir = env.create_dir("dir"); + const path sym = env.create_symlink("file", "sym"); + + { + directory_entry ent(file); + uintmax_t expect = hard_link_count(ent); + + // Remove the file to show that the results were already in the cache. + LIBCPP_ONLY(remove(file)); + + std::error_code ec = GetTestEC(); + TEST_CHECK(ent.hard_link_count(ec) == expect); + TEST_CHECK(!ec); + } + { + directory_entry ent(dir); + uintmax_t expect = hard_link_count(ent); + + LIBCPP_ONLY(remove(dir)); + + std::error_code ec = GetTestEC(); + TEST_CHECK(ent.hard_link_count(ec) == expect); + TEST_CHECK(!ec); + } + env.create_file("file", 99); + env.create_hardlink("file", "hl"); + { + directory_entry ent(sym); + std::error_code ec = GetTestEC(); + TEST_CHECK(ent.hard_link_count(ec) == 2); + TEST_CHECK(!ec); + } +} + +TEST_CASE(not_regular_file) { + using namespace fs; + + scoped_test_env env; + const path dir = env.create_dir("dir"); + const path dir2 = env.create_dir("dir/dir2"); + const path fifo = env.create_fifo("dir/fifo"); + const path sym_to_fifo = env.create_symlink("dir/fifo", "dir/sym"); + + const perms old_perms = status(dir).permissions(); + + for (auto p : {dir2, fifo, sym_to_fifo}) { + permissions(dir, old_perms); + std::error_code dummy_ec = GetTestEC(); + directory_entry ent(p, dummy_ec); + TEST_CHECK(!dummy_ec); + + uintmax_t expect = hard_link_count(p); + + LIBCPP_ONLY(permissions(dir, perms::none)); + + std::error_code ec = GetTestEC(); + TEST_CHECK(ent.hard_link_count(ec) == expect); + TEST_CHECK(!ec); + TEST_CHECK_NO_THROW(ent.hard_link_count()); + } +} + +TEST_CASE(error_reporting) { + using namespace fs; + + scoped_test_env env; + + const path dir = env.create_dir("dir"); + const path file = env.create_file("dir/file", 42); + const path file_out_of_dir = env.create_file("file2", 101); + const path sym_out_of_dir = env.create_symlink("dir/file", "sym"); + const path sym_in_dir = env.create_symlink("file2", "dir/sym2"); + + const perms old_perms = status(dir).permissions(); + + // test a file which doesn't exist + { + directory_entry ent; + + std::error_code ec = GetTestEC(); + ent.assign(StaticEnv::DNE, ec); + TEST_CHECK(ec); + TEST_REQUIRE(ent.path() == StaticEnv::DNE); + TEST_CHECK(ErrorIs(ec, std::errc::no_such_file_or_directory)); + + ec = GetTestEC(); + TEST_CHECK(ent.hard_link_count(ec) == uintmax_t(-1)); + TEST_CHECK(ErrorIs(ec, std::errc::no_such_file_or_directory)); + + ExceptionChecker Checker(StaticEnv::DNE, + std::errc::no_such_file_or_directory); + TEST_CHECK_THROW_RESULT(filesystem_error, Checker, ent.hard_link_count()); + } + // test a dead symlink + { + directory_entry ent; + + std::error_code ec = GetTestEC(); + uintmax_t expect_bad = hard_link_count(StaticEnv::BadSymlink, ec); + TEST_CHECK(expect_bad == uintmax_t(-1)); + TEST_CHECK(ErrorIs(ec, std::errc::no_such_file_or_directory)); + + ec = GetTestEC(); + ent.assign(StaticEnv::BadSymlink, ec); + TEST_REQUIRE(ent.path() == StaticEnv::BadSymlink); + TEST_CHECK(!ec); + + ec = GetTestEC(); + TEST_CHECK(ent.hard_link_count(ec) == expect_bad); + TEST_CHECK(ErrorIs(ec, std::errc::no_such_file_or_directory)); + + ExceptionChecker Checker(StaticEnv::BadSymlink, + std::errc::no_such_file_or_directory); + TEST_CHECK_THROW_RESULT(filesystem_error, Checker, ent.hard_link_count()); + } + // test a file w/o appropriate permissions. + { + directory_entry ent; + uintmax_t expect_good = hard_link_count(file); + permissions(dir, perms::none); + + std::error_code ec = GetTestEC(); + ent.assign(file, ec); + TEST_REQUIRE(ent.path() == file); + TEST_CHECK(ErrorIs(ec, std::errc::permission_denied)); + + ec = GetTestEC(); + TEST_CHECK(ent.hard_link_count(ec) == uintmax_t(-1)); + TEST_CHECK(ErrorIs(ec, std::errc::permission_denied)); + + ExceptionChecker Checker(file, std::errc::permission_denied); + TEST_CHECK_THROW_RESULT(filesystem_error, Checker, ent.hard_link_count()); + + permissions(dir, old_perms); + ec = GetTestEC(); + TEST_CHECK(ent.hard_link_count(ec) == expect_good); + TEST_CHECK(!ec); + TEST_CHECK_NO_THROW(ent.hard_link_count()); + } + permissions(dir, old_perms); + // test a symlink w/o appropriate permissions. + { + directory_entry ent; + uintmax_t expect_good = hard_link_count(sym_in_dir); + permissions(dir, perms::none); + + std::error_code ec = GetTestEC(); + ent.assign(sym_in_dir, ec); + TEST_REQUIRE(ent.path() == sym_in_dir); + TEST_CHECK(ErrorIs(ec, std::errc::permission_denied)); + + ec = GetTestEC(); + TEST_CHECK(ent.hard_link_count(ec) == uintmax_t(-1)); + TEST_CHECK(ErrorIs(ec, std::errc::permission_denied)); + + ExceptionChecker Checker(sym_in_dir, std::errc::permission_denied); + TEST_CHECK_THROW_RESULT(filesystem_error, Checker, ent.hard_link_count()); + + permissions(dir, old_perms); + ec = GetTestEC(); + TEST_CHECK(ent.hard_link_count(ec) == expect_good); + TEST_CHECK(!ec); + TEST_CHECK_NO_THROW(ent.hard_link_count()); + } + permissions(dir, old_perms); + // test a symlink to a file w/o appropriate permissions + { + directory_entry ent; + uintmax_t expect_good = hard_link_count(sym_out_of_dir); + permissions(dir, perms::none); + + std::error_code ec = GetTestEC(); + ent.assign(sym_out_of_dir, ec); + TEST_REQUIRE(ent.path() == sym_out_of_dir); + TEST_CHECK(!ec); + + ec = GetTestEC(); + TEST_CHECK(ent.hard_link_count(ec) == uintmax_t(-1)); + TEST_CHECK(ErrorIs(ec, std::errc::permission_denied)); + + ExceptionChecker Checker(sym_out_of_dir, std::errc::permission_denied); + TEST_CHECK_THROW_RESULT(filesystem_error, Checker, ent.hard_link_count()); + + permissions(dir, old_perms); + ec = GetTestEC(); + TEST_CHECK(ent.hard_link_count(ec) == expect_good); + TEST_CHECK(!ec); + TEST_CHECK_NO_THROW(ent.hard_link_count()); + } +} + +TEST_SUITE_END() Index: test/std/experimental/filesystem/class.directory_entry/directory_entry.obs/last_write_time.pass.cpp =================================================================== --- /dev/null +++ test/std/experimental/filesystem/class.directory_entry/directory_entry.obs/last_write_time.pass.cpp @@ -0,0 +1,210 @@ +//===----------------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++98, c++03 + +// + +// class directory_entry + +// file_time_type last_write_time() const; +// file_time_type last_write_time(error_code const&) const noexcept; + +#include "filesystem_include.hpp" +#include +#include + +#include "filesystem_test_helper.hpp" +#include "rapid-cxx-test.hpp" + +TEST_SUITE(directory_entry_obs_testsuite) + +TEST_CASE(signatures) { + using namespace fs; + { + const fs::directory_entry e; + std::error_code ec; + static_assert(std::is_same::value, + ""); + static_assert(std::is_same::value, + ""); + static_assert(noexcept(e.last_write_time()) == false, ""); + static_assert(noexcept(e.last_write_time(ec)) == true, ""); + } +} + +TEST_CASE(basic) { + using namespace fs; + + scoped_test_env env; + const path file = env.create_file("file", 42); + const path dir = env.create_dir("dir"); + const path sym = env.create_symlink("file", "sym"); + + { + directory_entry ent(file); + file_time_type expect = last_write_time(ent); + + // Remove the file to show that the results were already in the cache. + LIBCPP_ONLY(remove(file)); + + std::error_code ec = GetTestEC(); + TEST_CHECK(ent.last_write_time(ec) == expect); + TEST_CHECK(!ec); + } + { + directory_entry ent(dir); + file_time_type expect = last_write_time(ent); + + LIBCPP_ONLY(remove(dir)); + + std::error_code ec = GetTestEC(); + TEST_CHECK(ent.last_write_time(ec) == expect); + TEST_CHECK(!ec); + } + env.create_file("file", 99); + { + directory_entry ent(sym); + file_time_type expect = last_write_time(sym); + + std::error_code ec = GetTestEC(); + TEST_CHECK(ent.last_write_time(ec) == expect); + TEST_CHECK(!ec); + } +} + +TEST_CASE(error_reporting) { + using namespace fs; + + scoped_test_env env; + + const path dir = env.create_dir("dir"); + const path file = env.create_file("dir/file", 42); + const path file_out_of_dir = env.create_file("file2", 101); + const path sym_out_of_dir = env.create_symlink("dir/file", "sym"); + const path sym_in_dir = env.create_symlink("file2", "dir/sym2"); + + const perms old_perms = status(dir).permissions(); + + // test a file which doesn't exist + { + directory_entry ent; + + std::error_code ec = GetTestEC(); + ent.assign(StaticEnv::DNE, ec); + TEST_REQUIRE(ent.path() == StaticEnv::DNE); + TEST_CHECK(ErrorIs(ec, std::errc::no_such_file_or_directory)); + + ec = GetTestEC(); + TEST_CHECK(ent.last_write_time(ec) == file_time_type::min()); + TEST_CHECK(ErrorIs(ec, std::errc::no_such_file_or_directory)); + + ExceptionChecker Checker(StaticEnv::DNE, + std::errc::no_such_file_or_directory); + TEST_CHECK_THROW_RESULT(filesystem_error, Checker, ent.last_write_time()); + } + // test a dead symlink + { + directory_entry ent; + + std::error_code ec = GetTestEC(); + file_time_type expect_bad = last_write_time(StaticEnv::BadSymlink, ec); + TEST_CHECK(expect_bad == file_time_type::min()); + TEST_CHECK(ErrorIs(ec, std::errc::no_such_file_or_directory)); + + ec = GetTestEC(); + ent.assign(StaticEnv::BadSymlink, ec); + TEST_REQUIRE(ent.path() == StaticEnv::BadSymlink); + TEST_CHECK(!ec); + + ec = GetTestEC(); + TEST_CHECK(ent.last_write_time(ec) == expect_bad); + TEST_CHECK(ErrorIs(ec, std::errc::no_such_file_or_directory)); + + ExceptionChecker Checker(StaticEnv::BadSymlink, + std::errc::no_such_file_or_directory); + TEST_CHECK_THROW_RESULT(filesystem_error, Checker, ent.last_write_time()); + } + // test a file w/o appropriate permissions. + { + directory_entry ent; + file_time_type expect_good = last_write_time(file); + permissions(dir, perms::none); + + std::error_code ec = GetTestEC(); + ent.assign(file, ec); + TEST_REQUIRE(ent.path() == file); + TEST_CHECK(ErrorIs(ec, std::errc::permission_denied)); + + ec = GetTestEC(); + TEST_CHECK(ent.last_write_time(ec) == file_time_type::min()); + TEST_CHECK(ErrorIs(ec, std::errc::permission_denied)); + + ExceptionChecker Checker(file, std::errc::permission_denied); + TEST_CHECK_THROW_RESULT(filesystem_error, Checker, ent.last_write_time()); + + permissions(dir, old_perms); + ec = GetTestEC(); + TEST_CHECK(ent.last_write_time(ec) == expect_good); + TEST_CHECK(!ec); + TEST_CHECK_NO_THROW(ent.last_write_time()); + } + permissions(dir, old_perms); + // test a symlink w/o appropriate permissions. + { + directory_entry ent; + file_time_type expect_good = last_write_time(sym_in_dir); + permissions(dir, perms::none); + + std::error_code ec = GetTestEC(); + ent.assign(sym_in_dir, ec); + TEST_REQUIRE(ent.path() == sym_in_dir); + TEST_CHECK(ErrorIs(ec, std::errc::permission_denied)); + + ec = GetTestEC(); + TEST_CHECK(ent.last_write_time(ec) == file_time_type::min()); + TEST_CHECK(ErrorIs(ec, std::errc::permission_denied)); + + ExceptionChecker Checker(sym_in_dir, std::errc::permission_denied); + TEST_CHECK_THROW_RESULT(filesystem_error, Checker, ent.last_write_time()); + + permissions(dir, old_perms); + ec = GetTestEC(); + TEST_CHECK(ent.last_write_time(ec) == expect_good); + TEST_CHECK(!ec); + TEST_CHECK_NO_THROW(ent.last_write_time()); + } + permissions(dir, old_perms); + // test a symlink to a file w/o appropriate permissions + { + directory_entry ent; + file_time_type expect_good = last_write_time(sym_out_of_dir); + permissions(dir, perms::none); + + std::error_code ec = GetTestEC(); + ent.assign(sym_out_of_dir, ec); + TEST_REQUIRE(ent.path() == sym_out_of_dir); + TEST_CHECK(!ec); + + ec = GetTestEC(); + TEST_CHECK(ent.last_write_time(ec) == file_time_type::min()); + TEST_CHECK(ErrorIs(ec, std::errc::permission_denied)); + + ExceptionChecker Checker(sym_out_of_dir, std::errc::permission_denied); + TEST_CHECK_THROW_RESULT(filesystem_error, Checker, ent.last_write_time()); + + permissions(dir, old_perms); + ec = GetTestEC(); + TEST_CHECK(ent.last_write_time(ec) == expect_good); + TEST_CHECK(!ec); + TEST_CHECK_NO_THROW(ent.last_write_time()); + } +} + +TEST_SUITE_END() Index: test/std/experimental/filesystem/class.directory_entry/directory_entry.obs/status.pass.cpp =================================================================== --- test/std/experimental/filesystem/class.directory_entry/directory_entry.obs/status.pass.cpp +++ test/std/experimental/filesystem/class.directory_entry/directory_entry.obs/status.pass.cpp @@ -21,31 +21,38 @@ #include #include "filesystem_test_helper.hpp" +#include "rapid-cxx-test.hpp" -int main() -{ +TEST_SUITE(directory_entry_status_testsuite) + +TEST_CASE(test_basic) { using namespace fs; { - const directory_entry e("foo"); + const fs::directory_entry e("foo"); std::error_code ec; - static_assert(std::is_same::value, ""); - static_assert(std::is_same::value, ""); + static_assert(std::is_same::value, ""); + static_assert(std::is_same::value, ""); static_assert(noexcept(e.status()) == false, ""); static_assert(noexcept(e.status(ec)) == true, ""); } - auto TestFn = [](path const& p) { + path TestCases[] = {StaticEnv::File, StaticEnv::Dir, StaticEnv::SymlinkToFile, + StaticEnv::DNE}; + for (const auto& p : TestCases) { const directory_entry e(p); - std::error_code pec, eec; + std::error_code pec = GetTestEC(), eec = GetTestEC(1); file_status ps = fs::status(p, pec); file_status es = e.status(eec); - assert(ps.type() == es.type()); - assert(ps.permissions() == es.permissions()); - assert(pec == eec); - }; - { - TestFn(StaticEnv::File); - TestFn(StaticEnv::Dir); - TestFn(StaticEnv::SymlinkToFile); - TestFn(StaticEnv::DNE); + TEST_CHECK(ps.type() == es.type()); + TEST_CHECK(ps.permissions() == es.permissions()); + TEST_CHECK(pec == eec); + } + for (const auto& p : TestCases) { + const directory_entry e(p); + file_status ps = fs::status(p); + file_status es = e.status(); + TEST_CHECK(ps.type() == es.type()); + TEST_CHECK(ps.permissions() == es.permissions()); } } + +TEST_SUITE_END() Index: test/std/experimental/filesystem/class.directory_entry/directory_entry.obs/symlink_status.pass.cpp =================================================================== --- test/std/experimental/filesystem/class.directory_entry/directory_entry.obs/symlink_status.pass.cpp +++ test/std/experimental/filesystem/class.directory_entry/directory_entry.obs/symlink_status.pass.cpp @@ -21,8 +21,11 @@ #include #include "filesystem_test_helper.hpp" +#include "rapid-cxx-test.hpp" -int main() { +TEST_SUITE(directory_entry_obs_suite) + +TEST_CASE(test_signature) { using namespace fs; { const directory_entry e("foo"); @@ -32,19 +35,24 @@ static_assert(noexcept(e.symlink_status()) == false, ""); static_assert(noexcept(e.symlink_status(ec)) == true, ""); } - auto TestFn = [](path const& p) { + path TestCases[] = {StaticEnv::File, StaticEnv::Dir, StaticEnv::SymlinkToFile, + StaticEnv::DNE}; + for (const auto& p : TestCases) { const directory_entry e(p); - std::error_code pec, eec; + std::error_code pec = GetTestEC(), eec = GetTestEC(1); file_status ps = fs::symlink_status(p, pec); file_status es = e.symlink_status(eec); - assert(ps.type() == es.type()); - assert(ps.permissions() == es.permissions()); - assert(pec == eec); - }; - { - TestFn(StaticEnv::File); - TestFn(StaticEnv::Dir); - TestFn(StaticEnv::SymlinkToFile); - TestFn(StaticEnv::DNE); + TEST_CHECK(ps.type() == es.type()); + TEST_CHECK(ps.permissions() == es.permissions()); + TEST_CHECK(pec == eec); + } + for (const auto& p : TestCases) { + const directory_entry e(p); + file_status ps = fs::symlink_status(p); + file_status es = e.symlink_status(); + TEST_CHECK(ps.type() == es.type()); + TEST_CHECK(ps.permissions() == es.permissions()); } } + +TEST_SUITE_END() Index: test/std/experimental/filesystem/fs.op.funcs/fs.op.file_size/file_size.pass.cpp =================================================================== --- test/std/experimental/filesystem/fs.op.funcs/fs.op.file_size/file_size.pass.cpp +++ test/std/experimental/filesystem/fs.op.funcs/fs.op.file_size/file_size.pass.cpp @@ -62,17 +62,22 @@ TEST_CASE(file_size_error_cases) { - const path testCases[] = { - StaticEnv::Dir, - StaticEnv::SymlinkToDir, - StaticEnv::BadSymlink, - StaticEnv::DNE - }; + struct { + path p; + std::errc expected_err; + } TestCases[] = { + {StaticEnv::Dir, std::errc::is_a_directory}, + {StaticEnv::SymlinkToDir, std::errc::is_a_directory}, + {StaticEnv::BadSymlink, std::errc::no_such_file_or_directory}, + {StaticEnv::DNE, std::errc::no_such_file_or_directory}}; const uintmax_t expect = static_cast(-1); - for (auto& TC : testCases) { - std::error_code ec; - TEST_CHECK(file_size(TC, ec) == expect); - TEST_CHECK(ec); + for (auto& TC : TestCases) { + std::error_code ec = GetTestEC(); + TEST_CHECK(file_size(TC.p, ec) == expect); + TEST_CHECK(ErrorIs(ec, TC.expected_err)); + + ExceptionChecker Checker(TC.p, TC.expected_err); + TEST_CHECK_THROW_RESULT(filesystem_error, Checker, file_size(TC.p)); } } Index: test/support/filesystem_test_helper.hpp =================================================================== --- test/support/filesystem_test_helper.hpp +++ test/support/filesystem_test_helper.hpp @@ -8,6 +8,9 @@ #include #include #include +#include + +#include "rapid-cxx-test.hpp" // static test helpers @@ -381,8 +384,39 @@ // We often need to test that the error_code was cleared if no error occurs // this function returns an error_code which is set to an error that will // never be returned by the filesystem functions. -inline std::error_code GetTestEC() { - return std::make_error_code(std::errc::address_family_not_supported); +inline std::error_code GetTestEC(unsigned Idx = 0) { + using std::errc; + auto GetErrc = [&]() { + switch (Idx) { + case 0: + return errc::address_family_not_supported; + case 1: + return errc::address_not_available; + case 2: + return errc::address_in_use; + case 3: + return errc::argument_list_too_long; + default: + assert(false && "Idx out of range"); + std::abort(); + } + }; + return std::make_error_code(GetErrc()); +} + +inline bool ErrorIsImp(const std::error_code& ec, + std::vector const& errors) { + for (auto errc : errors) { + if (ec == std::make_error_code(errc)) + return true; + } + return false; +} + +template +inline bool ErrorIs(const std::error_code& ec, std::errc First, ErrcT... Rest) { + std::vector errors = {First, Rest...}; + return ErrorIsImp(ec, errors); } // Provide our own Sleep routine since std::this_thread::sleep_for is not @@ -403,4 +437,26 @@ return LHS.native() == RHS.native(); } +struct ExceptionChecker { + std::vector expected_err_list; + fs::path expected_path1; + fs::path expected_path2; + + template + explicit ExceptionChecker(fs::path p, std::errc first_err, ErrcT... rest_err) + : expected_err_list({first_err, rest_err...}), expected_path1(p) {} + + template + explicit ExceptionChecker(fs::path p1, fs::path p2, std::errc first_err, + ErrcT... rest_err) + : expected_err_list({first_err, rest_err...}), expected_path1(p1), + expected_path2(p2) {} + + void operator()(fs::filesystem_error const& Err) const { + TEST_CHECK(ErrorIsImp(Err.code(), expected_err_list)); + TEST_CHECK(Err.path1() == expected_path1); + TEST_CHECK(Err.path2() == expected_path2); + } +}; + #endif /* FILESYSTEM_TEST_HELPER_HPP */ Index: test/support/rapid-cxx-test.hpp =================================================================== --- test/support/rapid-cxx-test.hpp +++ test/support/rapid-cxx-test.hpp @@ -221,6 +221,24 @@ } while (false) # +#define TEST_CHECK_THROW_RESULT(Except, Checker, ...) \ + do { \ + TEST_SET_CHECKPOINT(); \ + ::rapid_cxx_test::test_outcome m_f(::rapid_cxx_test::failure_type::none, \ + __FILE__, TEST_FUNC_NAME(), __LINE__, \ + "TEST_CHECK_THROW_RESULT(" #Except \ + "," #Checker "," #__VA_ARGS__ ")", \ + ""); \ + try { \ + (static_cast(__VA_ARGS__)); \ + m_f.type = ::rapid_cxx_test::failure_type::check; \ + } catch (Except const& Caught) { \ + Checker(Caught); \ + } \ + ::rapid_cxx_test::get_reporter().report(m_f); \ + } while (false) +# + #else // TEST_HAS_NO_EXCEPTIONS # define TEST_CHECK_NO_THROW(...) \ @@ -236,6 +254,7 @@ # #define TEST_CHECK_THROW(Except, ...) ((void)0) +#define TEST_CHECK_THROW_RESULT(Except, Checker, ...) ((void)0) #endif // TEST_HAS_NO_EXCEPTIONS Index: www/cxx1z_status.html =================================================================== --- www/cxx1z_status.html +++ www/cxx1z_status.html @@ -148,7 +148,7 @@ P0156R2LWGVariadic Lock guard(rev 5)KonaComplete5.0 P0270R3CWGRemoving C dependencies from signal handler wordingKona P0298R3CWGA byte type definitionKonaComplete5.0 - P0317R1LWGDirectory Entry Caching for FilesystemKona + P0317R1LWGDirectory Entry Caching for FilesystemKonaComplete7.0 P0430R2LWGFile system library on non-POSIX-like operating systemsKonaComplete7.0 P0433R2LWGToward a resolution of US7 and US14: Integrating template deduction for class templates into the standard libraryKonaIn progress7.0 P0452R1LWGUnifying <numeric> Parallel AlgorithmsKona