Please use GitHub pull requests for new patches. Avoid migrating existing patches. Phabricator shutdown timeline
Differential D49774 Diff 157349 test/std/experimental/filesystem/fs.op.funcs/fs.op.last_write_time/last_write_time.pass.cpp
Changeset View
Changeset View
Standalone View
Standalone View
test/std/experimental/filesystem/fs.op.funcs/fs.op.last_write_time/last_write_time.pass.cpp
Show All 24 Lines | |||||
#include "test_macros.h" | #include "test_macros.h" | ||||
#include "rapid-cxx-test.hpp" | #include "rapid-cxx-test.hpp" | ||||
#include "filesystem_test_helper.hpp" | #include "filesystem_test_helper.hpp" | ||||
#include <sys/stat.h> | #include <sys/stat.h> | ||||
#include <iostream> | #include <iostream> | ||||
#include <fcntl.h> | |||||
#include <sys/time.h> | |||||
using namespace fs; | using namespace fs; | ||||
struct Times { std::time_t access, write; }; | using TimeSpec = struct ::timespec; | ||||
using StatT = struct ::stat; | |||||
using Sec = std::chrono::duration<file_time_type::rep>; | |||||
using Hours = std::chrono::hours; | |||||
using Minutes = std::chrono::minutes; | |||||
using MicroSec = std::chrono::duration<file_time_type::rep, std::micro>; | |||||
using NanoSec = std::chrono::duration<file_time_type::rep, std::nano>; | |||||
using std::chrono::duration_cast; | |||||
#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 | |||||
bool ConvertToTimeSpec(TimeSpec& ts, file_time_type ft) { | |||||
using SecFieldT = decltype(TimeSpec::tv_sec); | |||||
using NSecFieldT = decltype(TimeSpec::tv_nsec); | |||||
using SecLim = std::numeric_limits<SecFieldT>; | |||||
using NSecLim = std::numeric_limits<NSecFieldT>; | |||||
auto secs = duration_cast<Sec>(ft.time_since_epoch()); | |||||
auto nsecs = duration_cast<NanoSec>(ft.time_since_epoch() - secs); | |||||
if (nsecs.count() < 0) { | |||||
if (Sec::min().count() > SecLim::min()) { | |||||
secs += Sec(1); | |||||
nsecs -= Sec(1); | |||||
} else { | |||||
nsecs = NanoSec(0); | |||||
} | |||||
} | |||||
if (SecLim::max() < secs.count() || SecLim::min() > secs.count()) | |||||
return false; | |||||
if (NSecLim::max() < nsecs.count() || NSecLim::min() > nsecs.count()) | |||||
return false; | |||||
ts.tv_sec = secs.count(); | |||||
ts.tv_nsec = nsecs.count(); | |||||
return true; | |||||
} | |||||
bool ConvertFromTimeSpec(file_time_type& ft, TimeSpec ts) { | |||||
auto secs_part = duration_cast<file_time_type::duration>(Sec(ts.tv_sec)); | |||||
if (duration_cast<Sec>(secs_part).count() != ts.tv_sec) | |||||
return false; | |||||
auto subsecs = duration_cast<file_time_type::duration>(NanoSec(ts.tv_nsec)); | |||||
auto dur = secs_part + subsecs; | |||||
if (dur < secs_part && subsecs.count() >= 0) | |||||
return false; | |||||
ft = file_time_type(dur); | |||||
return true; | |||||
} | |||||
bool CompareTimeExact(TimeSpec ts, TimeSpec ts2) { | |||||
return ts2.tv_sec == ts.tv_sec && ts2.tv_nsec == ts.tv_nsec; | |||||
} | |||||
bool CompareTimeExact(file_time_type ft, TimeSpec ts) { | |||||
TimeSpec ts2 = {}; | |||||
if (!ConvertToTimeSpec(ts2, ft)) | |||||
return false; | |||||
return CompareTimeExact(ts, ts2); | |||||
} | |||||
bool CompareTimeExact(TimeSpec ts, file_time_type ft) { | |||||
return CompareTimeExact(ft, ts); | |||||
} | |||||
struct Times { | |||||
TimeSpec access, write; | |||||
}; | |||||
Times GetTimes(path const& p) { | Times GetTimes(path const& p) { | ||||
using Clock = file_time_type::clock; | using Clock = file_time_type::clock; | ||||
struct ::stat st; | StatT st; | ||||
if (::stat(p.c_str(), &st) == -1) { | if (::stat(p.c_str(), &st) == -1) { | ||||
std::error_code ec(errno, std::generic_category()); | std::error_code ec(errno, std::generic_category()); | ||||
#ifndef TEST_HAS_NO_EXCEPTIONS | #ifndef TEST_HAS_NO_EXCEPTIONS | ||||
throw ec; | throw ec; | ||||
#else | #else | ||||
std::cerr << ec.message() << std::endl; | std::cerr << ec.message() << std::endl; | ||||
std::exit(EXIT_FAILURE); | std::exit(EXIT_FAILURE); | ||||
#endif | #endif | ||||
} | } | ||||
return {st.st_atime, st.st_mtime}; | return {extract_atime(st), extract_mtime(st)}; | ||||
} | } | ||||
std::time_t LastAccessTime(path const& p) { | TimeSpec LastAccessTime(path const& p) { return GetTimes(p).access; } | ||||
return GetTimes(p).access; | |||||
} | |||||
std::time_t LastWriteTime(path const& p) { | TimeSpec LastWriteTime(path const& p) { return GetTimes(p).write; } | ||||
return GetTimes(p).write; | |||||
} | |||||
std::pair<std::time_t, std::time_t> GetSymlinkTimes(path const& p) { | std::pair<TimeSpec, TimeSpec> GetSymlinkTimes(path const& p) { | ||||
using Clock = file_time_type::clock; | using Clock = file_time_type::clock; | ||||
struct ::stat st; | StatT st; | ||||
if (::lstat(p.c_str(), &st) == -1) { | if (::lstat(p.c_str(), &st) == -1) { | ||||
std::error_code ec(errno, std::generic_category()); | std::error_code ec(errno, std::generic_category()); | ||||
#ifndef TEST_HAS_NO_EXCEPTIONS | #ifndef TEST_HAS_NO_EXCEPTIONS | ||||
throw ec; | throw ec; | ||||
#else | #else | ||||
std::cerr << ec.message() << std::endl; | std::cerr << ec.message() << std::endl; | ||||
std::exit(EXIT_FAILURE); | std::exit(EXIT_FAILURE); | ||||
#endif | #endif | ||||
} | } | ||||
return {st.st_atime, st.st_mtime}; | return {extract_atime(st), extract_mtime(st)}; | ||||
} | } | ||||
namespace { | namespace { | ||||
bool TestSupportsNegativeTimes() { | |||||
// In some configurations, the comparison is tautological and the test is valid. | |||||
// We disable the warning so that we can actually test it regardless. Also, that | |||||
// diagnostic is pretty new, so also don't fail if old clang does not support it | |||||
#if defined(__clang__) | |||||
#pragma clang diagnostic push | |||||
#pragma clang diagnostic ignored "-Wunknown-warning-option" | |||||
#pragma clang diagnostic ignored "-Wunknown-pragmas" | |||||
#pragma clang diagnostic ignored "-Wtautological-constant-compare" | |||||
#endif | |||||
static const bool SupportsNegativeTimes = [] { | |||||
using namespace std::chrono; | using namespace std::chrono; | ||||
std::error_code ec; | std::error_code ec; | ||||
std::time_t old_write_time, new_write_time; | TimeSpec old_write_time, new_write_time; | ||||
{ // WARNING: Do not assert in this scope. | { // WARNING: Do not assert in this scope. | ||||
scoped_test_env env; | scoped_test_env env; | ||||
const path file = env.create_file("file", 42); | const path file = env.create_file("file", 42); | ||||
old_write_time = LastWriteTime(file); | old_write_time = LastWriteTime(file); | ||||
file_time_type tp(seconds(-5)); | file_time_type tp(seconds(-5)); | ||||
fs::last_write_time(file, tp, ec); | fs::last_write_time(file, tp, ec); | ||||
new_write_time = LastWriteTime(file); | new_write_time = LastWriteTime(file); | ||||
} | } | ||||
return !ec && new_write_time <= -5; | |||||
} | |||||
// In some configurations, the comparison is tautological and the test is valid. | return !ec && new_write_time.tv_sec < 0; | ||||
// We disable the warning so that we can actually test it regardless. Also, that | }(); | ||||
// diagnostic is pretty new, so also don't fail if old clang does not support it | |||||
#if defined(__clang__) | |||||
#pragma clang diagnostic push | |||||
#pragma clang diagnostic ignored "-Wunknown-warning-option" | |||||
#pragma clang diagnostic ignored "-Wunknown-pragmas" | |||||
#pragma clang diagnostic ignored "-Wtautological-constant-compare" | |||||
#endif | |||||
bool TestSupportsMaxTime() { | static const bool SupportsMaxTime = [] { | ||||
using namespace std::chrono; | using namespace std::chrono; | ||||
using Lim = std::numeric_limits<std::time_t>; | TimeSpec max_ts = {}; | ||||
auto max_sec = duration_cast<seconds>(file_time_type::max().time_since_epoch()).count(); | if (!ConvertToTimeSpec(max_ts, file_time_type::max())) | ||||
if (max_sec > Lim::max()) return false; | return false; | ||||
std::error_code ec; | std::error_code ec; | ||||
std::time_t old_write_time, new_write_time; | TimeSpec old_write_time, new_write_time; | ||||
{ // WARNING: Do not assert in this scope. | { // WARNING: Do not assert in this scope. | ||||
scoped_test_env env; | scoped_test_env env; | ||||
const path file = env.create_file("file", 42); | const path file = env.create_file("file", 42); | ||||
old_write_time = LastWriteTime(file); | old_write_time = LastWriteTime(file); | ||||
file_time_type tp = file_time_type::max(); | file_time_type tp = file_time_type::max(); | ||||
fs::last_write_time(file, tp, ec); | fs::last_write_time(file, tp, ec); | ||||
new_write_time = LastWriteTime(file); | new_write_time = LastWriteTime(file); | ||||
} | } | ||||
return !ec && new_write_time > max_sec - 1; | return !ec && new_write_time.tv_sec > max_ts.tv_sec - 1; | ||||
} | }(); | ||||
bool TestSupportsMinTime() { | static const bool SupportsMinTime = [] { | ||||
using namespace std::chrono; | using namespace std::chrono; | ||||
using Lim = std::numeric_limits<std::time_t>; | TimeSpec min_ts = {}; | ||||
auto min_sec = duration_cast<seconds>(file_time_type::min().time_since_epoch()).count(); | if (!ConvertToTimeSpec(min_ts, file_time_type::min())) | ||||
if (min_sec < Lim::min()) return false; | return false; | ||||
std::error_code ec; | std::error_code ec; | ||||
std::time_t old_write_time, new_write_time; | TimeSpec old_write_time, new_write_time; | ||||
{ // WARNING: Do not assert in this scope. | { // WARNING: Do not assert in this scope. | ||||
scoped_test_env env; | scoped_test_env env; | ||||
const path file = env.create_file("file", 42); | const path file = env.create_file("file", 42); | ||||
old_write_time = LastWriteTime(file); | old_write_time = LastWriteTime(file); | ||||
file_time_type tp = file_time_type::min(); | file_time_type tp = file_time_type::min(); | ||||
fs::last_write_time(file, tp, ec); | fs::last_write_time(file, tp, ec); | ||||
new_write_time = LastWriteTime(file); | new_write_time = LastWriteTime(file); | ||||
} | } | ||||
return !ec && new_write_time < min_sec + 1; | return !ec && new_write_time.tv_sec < min_ts.tv_sec + 1; | ||||
} | }(); | ||||
#if defined(__clang__) | static const bool SupportsNanosecondRoundTrip = [] { | ||||
#pragma clang diagnostic pop | NanoSec ns(3); | ||||
#endif | |||||
static const bool SupportsNegativeTimes = TestSupportsNegativeTimes(); | // Test if the file_time_type period is less than that of nanoseconds. | ||||
static const bool SupportsMaxTime = TestSupportsMaxTime(); | auto ft_dur = duration_cast<file_time_type::duration>(ns); | ||||
static const bool SupportsMinTime = TestSupportsMinTime(); | if (duration_cast<NanoSec>(ft_dur) != ns) | ||||
return false; | |||||
// Test that the system call we use to set the times also supports nanosecond | |||||
// resolution. (utimes does not) | |||||
file_time_type ft(ft_dur); | |||||
{ | |||||
scoped_test_env env; | |||||
const path p = env.create_file("file", 42); | |||||
last_write_time(p, ft); | |||||
return last_write_time(p) == ft; | |||||
} | |||||
}(); | |||||
static const bool SupportsMinRoundTrip = [] { | |||||
TimeSpec ts = {}; | |||||
if (!ConvertToTimeSpec(ts, file_time_type::min())) | |||||
return false; | |||||
file_time_type min_val = {}; | |||||
if (!ConvertFromTimeSpec(min_val, ts)) | |||||
return false; | |||||
return min_val == file_time_type::min(); | |||||
}(); | |||||
} // end namespace | } // end namespace | ||||
// In some configurations, the comparison is tautological and the test is valid. | static bool CompareTime(TimeSpec t1, TimeSpec t2) { | ||||
// We disable the warning so that we can actually test it regardless. Also, that | if (SupportsNanosecondRoundTrip) | ||||
// diagnostic is pretty new, so also don't fail if old clang does not support it | return CompareTimeExact(t1, t2); | ||||
#if defined(__clang__) | if (t1.tv_sec != t2.tv_sec) | ||||
#pragma clang diagnostic push | return false; | ||||
#pragma clang diagnostic ignored "-Wunknown-warning-option" | |||||
#pragma clang diagnostic ignored "-Wunknown-pragmas" | auto diff = std::abs(t1.tv_nsec - t2.tv_nsec); | ||||
#pragma clang diagnostic ignored "-Wtautological-constant-compare" | |||||
#endif | return diff < duration_cast<NanoSec>(MicroSec(1)).count(); | ||||
} | |||||
static bool CompareTime(file_time_type t1, TimeSpec t2) { | |||||
TimeSpec ts1 = {}; | |||||
if (!ConvertToTimeSpec(ts1, t1)) | |||||
return false; | |||||
return CompareTime(ts1, t2); | |||||
} | |||||
static bool CompareTime(TimeSpec t1, file_time_type t2) { | |||||
return CompareTime(t2, t1); | |||||
} | |||||
static bool CompareTime(file_time_type t1, file_time_type t2) { | |||||
auto min_secs = duration_cast<Sec>(file_time_type::min().time_since_epoch()); | |||||
bool IsMin = | |||||
t1.time_since_epoch() < min_secs || t2.time_since_epoch() < min_secs; | |||||
if (SupportsNanosecondRoundTrip && (!IsMin || SupportsMinRoundTrip)) | |||||
return t1 == t2; | |||||
if (IsMin) { | |||||
return duration_cast<Sec>(t1.time_since_epoch()) == | |||||
duration_cast<Sec>(t2.time_since_epoch()); | |||||
} | |||||
file_time_type::duration dur; | |||||
if (t1 > t2) | |||||
dur = t1 - t2; | |||||
else | |||||
dur = t2 - t1; | |||||
return duration_cast<MicroSec>(dur).count() < 1; | |||||
} | |||||
// Check if a time point is representable on a given filesystem. Check that: | // Check if a time point is representable on a given filesystem. Check that: | ||||
// (A) 'tp' is representable as a time_t | // (A) 'tp' is representable as a time_t | ||||
// (B) 'tp' is non-negative or the filesystem supports negative times. | // (B) 'tp' is non-negative or the filesystem supports negative times. | ||||
// (C) 'tp' is not 'file_time_type::max()' or the filesystem supports the max | // (C) 'tp' is not 'file_time_type::max()' or the filesystem supports the max | ||||
// value. | // value. | ||||
// (D) 'tp' is not 'file_time_type::min()' or the filesystem supports the min | // (D) 'tp' is not 'file_time_type::min()' or the filesystem supports the min | ||||
// value. | // value. | ||||
inline bool TimeIsRepresentableByFilesystem(file_time_type tp) { | inline bool TimeIsRepresentableByFilesystem(file_time_type tp) { | ||||
using namespace std::chrono; | TimeSpec ts = {}; | ||||
using Lim = std::numeric_limits<std::time_t>; | if (!ConvertToTimeSpec(ts, tp)) | ||||
auto sec = duration_cast<seconds>(tp.time_since_epoch()).count(); | return false; | ||||
auto microsec = duration_cast<microseconds>(tp.time_since_epoch()).count(); | else if (tp.time_since_epoch().count() < 0 && !SupportsNegativeTimes) | ||||
if (sec < Lim::min() || sec > Lim::max()) return false; | return false; | ||||
else if (microsec < 0 && !SupportsNegativeTimes) return false; | else if (tp == file_time_type::max() && !SupportsMaxTime) | ||||
else if (tp == file_time_type::max() && !SupportsMaxTime) return false; | return false; | ||||
else if (tp == file_time_type::min() && !SupportsMinTime) return false; | else if (tp == file_time_type::min() && !SupportsMinTime) | ||||
return false; | |||||
return true; | return true; | ||||
} | } | ||||
#if defined(__clang__) | #if defined(__clang__) | ||||
#pragma clang diagnostic pop | #pragma clang diagnostic pop | ||||
#endif | #endif | ||||
TEST_SUITE(exists_test_suite) | // Create a sub-second duration using the smallest period the filesystem supports. | ||||
file_time_type::duration SubSec(long long val) { | |||||
using SubSecT = file_time_type::duration; | |||||
if (SupportsNanosecondRoundTrip) { | |||||
return duration_cast<SubSecT>(NanoSec(val)); | |||||
} else { | |||||
return duration_cast<SubSecT>(MicroSec(val)); | |||||
} | |||||
} | |||||
TEST_SUITE(last_write_time_test_suite) | |||||
TEST_CASE(signature_test) | TEST_CASE(signature_test) | ||||
{ | { | ||||
const file_time_type t; | const file_time_type t; | ||||
const path p; ((void)p); | const path p; ((void)p); | ||||
std::error_code ec; ((void)ec); | std::error_code ec; ((void)ec); | ||||
ASSERT_SAME_TYPE(decltype(last_write_time(p)), file_time_type); | ASSERT_SAME_TYPE(decltype(last_write_time(p)), file_time_type); | ||||
ASSERT_SAME_TYPE(decltype(last_write_time(p, ec)), file_time_type); | ASSERT_SAME_TYPE(decltype(last_write_time(p, ec)), file_time_type); | ||||
ASSERT_SAME_TYPE(decltype(last_write_time(p, t)), void); | ASSERT_SAME_TYPE(decltype(last_write_time(p, t)), void); | ||||
ASSERT_SAME_TYPE(decltype(last_write_time(p, t, ec)), void); | ASSERT_SAME_TYPE(decltype(last_write_time(p, t, ec)), void); | ||||
ASSERT_NOT_NOEXCEPT(last_write_time(p)); | ASSERT_NOT_NOEXCEPT(last_write_time(p)); | ||||
ASSERT_NOT_NOEXCEPT(last_write_time(p, t)); | ASSERT_NOT_NOEXCEPT(last_write_time(p, t)); | ||||
ASSERT_NOEXCEPT(last_write_time(p, ec)); | ASSERT_NOEXCEPT(last_write_time(p, ec)); | ||||
ASSERT_NOEXCEPT(last_write_time(p, t, ec)); | ASSERT_NOEXCEPT(last_write_time(p, t, ec)); | ||||
} | } | ||||
TEST_CASE(read_last_write_time_static_env_test) | TEST_CASE(read_last_write_time_static_env_test) | ||||
{ | { | ||||
using C = file_time_type::clock; | using C = file_time_type::clock; | ||||
file_time_type min = file_time_type::min(); | file_time_type min = file_time_type::min(); | ||||
{ | { | ||||
file_time_type ret = last_write_time(StaticEnv::File); | file_time_type ret = last_write_time(StaticEnv::File); | ||||
TEST_CHECK(ret != min); | TEST_CHECK(ret != min); | ||||
TEST_CHECK(ret < C::now()); | TEST_CHECK(ret < C::now()); | ||||
TEST_CHECK(C::to_time_t(ret) == LastWriteTime(StaticEnv::File)); | TEST_CHECK(CompareTime(ret, LastWriteTime(StaticEnv::File))); | ||||
file_time_type ret2 = last_write_time(StaticEnv::SymlinkToFile); | file_time_type ret2 = last_write_time(StaticEnv::SymlinkToFile); | ||||
TEST_CHECK(ret == ret2); | TEST_CHECK(CompareTime(ret, ret2)); | ||||
TEST_CHECK(C::to_time_t(ret2) == LastWriteTime(StaticEnv::SymlinkToFile)); | TEST_CHECK(CompareTime(ret2, LastWriteTime(StaticEnv::SymlinkToFile))); | ||||
} | } | ||||
{ | { | ||||
file_time_type ret = last_write_time(StaticEnv::Dir); | file_time_type ret = last_write_time(StaticEnv::Dir); | ||||
TEST_CHECK(ret != min); | TEST_CHECK(ret != min); | ||||
TEST_CHECK(ret < C::now()); | TEST_CHECK(ret < C::now()); | ||||
TEST_CHECK(C::to_time_t(ret) == LastWriteTime(StaticEnv::Dir)); | TEST_CHECK(CompareTime(ret, LastWriteTime(StaticEnv::Dir))); | ||||
file_time_type ret2 = last_write_time(StaticEnv::SymlinkToDir); | file_time_type ret2 = last_write_time(StaticEnv::SymlinkToDir); | ||||
TEST_CHECK(ret == ret2); | TEST_CHECK(CompareTime(ret, ret2)); | ||||
TEST_CHECK(C::to_time_t(ret2) == LastWriteTime(StaticEnv::SymlinkToDir)); | TEST_CHECK(CompareTime(ret2, LastWriteTime(StaticEnv::SymlinkToDir))); | ||||
} | } | ||||
} | } | ||||
TEST_CASE(get_last_write_time_dynamic_env_test) | TEST_CASE(get_last_write_time_dynamic_env_test) | ||||
{ | { | ||||
using Clock = file_time_type::clock; | using Clock = file_time_type::clock; | ||||
using Sec = std::chrono::seconds; | using Sec = std::chrono::seconds; | ||||
scoped_test_env env; | scoped_test_env env; | ||||
const path file = env.create_file("file", 42); | const path file = env.create_file("file", 42); | ||||
const path dir = env.create_dir("dir"); | const path dir = env.create_dir("dir"); | ||||
const auto file_times = GetTimes(file); | const auto file_times = GetTimes(file); | ||||
const std::time_t file_write_time = file_times.write; | const TimeSpec file_write_time = file_times.write; | ||||
const auto dir_times = GetTimes(dir); | const auto dir_times = GetTimes(dir); | ||||
const std::time_t dir_write_time = dir_times.write; | const TimeSpec dir_write_time = dir_times.write; | ||||
file_time_type ftime = last_write_time(file); | file_time_type ftime = last_write_time(file); | ||||
TEST_CHECK(Clock::to_time_t(ftime) == file_write_time); | TEST_CHECK(Clock::to_time_t(ftime) == file_write_time.tv_sec); | ||||
TEST_CHECK(CompareTime(ftime, file_write_time)); | |||||
file_time_type dtime = last_write_time(dir); | file_time_type dtime = last_write_time(dir); | ||||
TEST_CHECK(Clock::to_time_t(dtime) == dir_write_time); | TEST_CHECK(Clock::to_time_t(dtime) == dir_write_time.tv_sec); | ||||
TEST_CHECK(CompareTime(dtime, dir_write_time)); | |||||
SleepFor(Sec(2)); | SleepFor(Sec(2)); | ||||
// update file and add a file to the directory. Make sure the times increase. | // update file and add a file to the directory. Make sure the times increase. | ||||
std::ofstream of(file, std::ofstream::app); | std::ofstream of(file, std::ofstream::app); | ||||
of << "hello"; | of << "hello"; | ||||
of.close(); | of.close(); | ||||
env.create_file("dir/file1", 1); | env.create_file("dir/file1", 1); | ||||
file_time_type ftime2 = last_write_time(file); | file_time_type ftime2 = last_write_time(file); | ||||
file_time_type dtime2 = last_write_time(dir); | file_time_type dtime2 = last_write_time(dir); | ||||
TEST_CHECK(ftime2 > ftime); | TEST_CHECK(ftime2 > ftime); | ||||
TEST_CHECK(dtime2 > dtime); | TEST_CHECK(dtime2 > dtime); | ||||
TEST_CHECK(LastWriteTime(file) == Clock::to_time_t(ftime2)); | TEST_CHECK(CompareTime(LastWriteTime(file), ftime2)); | ||||
TEST_CHECK(LastWriteTime(dir) == Clock::to_time_t(dtime2)); | TEST_CHECK(CompareTime(LastWriteTime(dir), dtime2)); | ||||
} | } | ||||
TEST_CASE(set_last_write_time_dynamic_env_test) | TEST_CASE(set_last_write_time_dynamic_env_test) | ||||
{ | { | ||||
using Clock = file_time_type::clock; | using Clock = file_time_type::clock; | ||||
using Sec = std::chrono::seconds; | using SubSecT = file_time_type::duration; | ||||
using Hours = std::chrono::hours; | |||||
using Minutes = std::chrono::minutes; | |||||
using MicroSec = std::chrono::microseconds; | |||||
scoped_test_env env; | scoped_test_env env; | ||||
const path file = env.create_file("file", 42); | const path file = env.create_file("file", 42); | ||||
const path dir = env.create_dir("dir"); | const path dir = env.create_dir("dir"); | ||||
const auto now = Clock::now(); | const auto now = Clock::now(); | ||||
const file_time_type epoch_time = now - now.time_since_epoch(); | const file_time_type epoch_time = now - now.time_since_epoch(); | ||||
const file_time_type future_time = now + Hours(3) + Sec(42) + MicroSec(17); | const file_time_type future_time = now + Hours(3) + Sec(42) + SubSec(17); | ||||
const file_time_type past_time = now - Minutes(3) - Sec(42) - MicroSec(17); | const file_time_type past_time = now - Minutes(3) - Sec(42) - SubSec(17); | ||||
const file_time_type before_epoch_time = epoch_time - Minutes(3) - Sec(42) - MicroSec(17); | const file_time_type before_epoch_time = | ||||
epoch_time - Minutes(3) - Sec(42) - SubSec(17); | |||||
// FreeBSD has a bug in their utimes implementation where the time is not update | // FreeBSD has a bug in their utimes implementation where the time is not update | ||||
// when the number of seconds is '-1'. | // when the number of seconds is '-1'. | ||||
#if defined(__FreeBSD__) | #if defined(__FreeBSD__) | ||||
const file_time_type just_before_epoch_time = epoch_time - Sec(2) - MicroSec(17); | const file_time_type just_before_epoch_time = | ||||
epoch_time - Sec(2) - SubSec(17); | |||||
#else | #else | ||||
const file_time_type just_before_epoch_time = epoch_time - MicroSec(17); | const file_time_type just_before_epoch_time = epoch_time - SubSec(17); | ||||
#endif | #endif | ||||
struct TestCase { | struct TestCase { | ||||
path p; | path p; | ||||
file_time_type new_time; | file_time_type new_time; | ||||
} cases[] = { | } cases[] = { | ||||
{file, epoch_time}, | {file, epoch_time}, | ||||
{dir, epoch_time}, | {dir, epoch_time}, | ||||
{file, future_time}, | {file, future_time}, | ||||
{dir, future_time}, | {dir, future_time}, | ||||
{file, past_time}, | {file, past_time}, | ||||
{dir, past_time}, | {dir, past_time}, | ||||
{file, before_epoch_time}, | {file, before_epoch_time}, | ||||
{dir, before_epoch_time}, | {dir, before_epoch_time}, | ||||
{file, just_before_epoch_time}, | {file, just_before_epoch_time}, | ||||
{dir, just_before_epoch_time} | {dir, just_before_epoch_time} | ||||
}; | }; | ||||
for (const auto& TC : cases) { | for (const auto& TC : cases) { | ||||
const auto old_times = GetTimes(TC.p); | const auto old_times = GetTimes(TC.p); | ||||
file_time_type old_time(Sec(old_times.write)); | file_time_type old_time; | ||||
TEST_REQUIRE(ConvertFromTimeSpec(old_time, old_times.write)); | |||||
std::error_code ec = GetTestEC(); | std::error_code ec = GetTestEC(); | ||||
last_write_time(TC.p, TC.new_time, ec); | last_write_time(TC.p, TC.new_time, ec); | ||||
TEST_CHECK(!ec); | TEST_CHECK(!ec); | ||||
file_time_type got_time = last_write_time(TC.p); | file_time_type got_time = last_write_time(TC.p); | ||||
if (TimeIsRepresentableByFilesystem(TC.new_time)) { | if (TimeIsRepresentableByFilesystem(TC.new_time)) { | ||||
TEST_CHECK(got_time != old_time); | TEST_CHECK(got_time != old_time); | ||||
if (TC.new_time < epoch_time) { | TEST_CHECK(CompareTime(got_time, TC.new_time)); | ||||
TEST_CHECK(got_time <= TC.new_time); | TEST_CHECK(CompareTime(LastAccessTime(TC.p), old_times.access)); | ||||
TEST_CHECK(got_time > TC.new_time - Sec(1)); | |||||
} else { | |||||
TEST_CHECK(got_time <= TC.new_time + Sec(1)); | |||||
TEST_CHECK(got_time >= TC.new_time - Sec(1)); | |||||
} | |||||
TEST_CHECK(LastAccessTime(TC.p) == old_times.access); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
TEST_CASE(last_write_time_symlink_test) | TEST_CASE(last_write_time_symlink_test) | ||||
{ | { | ||||
using Clock = file_time_type::clock; | using Clock = file_time_type::clock; | ||||
using Sec = std::chrono::seconds; | |||||
using Hours = std::chrono::hours; | |||||
using Minutes = std::chrono::minutes; | |||||
scoped_test_env env; | scoped_test_env env; | ||||
const path file = env.create_file("file", 42); | const path file = env.create_file("file", 42); | ||||
const path sym = env.create_symlink("file", "sym"); | const path sym = env.create_symlink("file", "sym"); | ||||
const file_time_type new_time = Clock::now() + Hours(3); | const file_time_type new_time = Clock::now() + Hours(3); | ||||
const auto old_times = GetTimes(sym); | const auto old_times = GetTimes(sym); | ||||
const auto old_sym_times = GetSymlinkTimes(sym); | const auto old_sym_times = GetSymlinkTimes(sym); | ||||
std::error_code ec = GetTestEC(); | std::error_code ec = GetTestEC(); | ||||
last_write_time(sym, new_time, ec); | last_write_time(sym, new_time, ec); | ||||
TEST_CHECK(!ec); | TEST_CHECK(!ec); | ||||
const std::time_t new_time_t = Clock::to_time_t(new_time); | |||||
file_time_type got_time = last_write_time(sym); | file_time_type got_time = last_write_time(sym); | ||||
std::time_t got_time_t = Clock::to_time_t(got_time); | TEST_CHECK(!CompareTime(got_time, old_times.write)); | ||||
TEST_CHECK(got_time == new_time); | |||||
TEST_CHECK(got_time_t != old_times.write); | TEST_CHECK(CompareTime(LastWriteTime(file), new_time)); | ||||
TEST_CHECK(got_time_t == new_time_t); | TEST_CHECK(CompareTime(LastAccessTime(sym), old_times.access)); | ||||
TEST_CHECK(LastWriteTime(file) == new_time_t); | std::pair<TimeSpec, TimeSpec> sym_times = GetSymlinkTimes(sym); | ||||
TEST_CHECK(LastAccessTime(sym) == old_times.access); | TEST_CHECK(CompareTime(sym_times.first, old_sym_times.first)); | ||||
TEST_CHECK(GetSymlinkTimes(sym) == old_sym_times); | TEST_CHECK(CompareTime(sym_times.second, old_sym_times.second)); | ||||
} | } | ||||
TEST_CASE(test_write_min_time) | TEST_CASE(test_write_min_time) | ||||
{ | { | ||||
using Clock = file_time_type::clock; | using Clock = file_time_type::clock; | ||||
using Sec = std::chrono::seconds; | |||||
using MicroSec = std::chrono::microseconds; | |||||
using Lim = std::numeric_limits<std::time_t>; | |||||
scoped_test_env env; | scoped_test_env env; | ||||
const path p = env.create_file("file", 42); | const path p = env.create_file("file", 42); | ||||
const file_time_type old_time = last_write_time(p); | |||||
std::error_code ec = GetTestEC(); | |||||
file_time_type new_time = file_time_type::min(); | file_time_type new_time = file_time_type::min(); | ||||
std::error_code ec = GetTestEC(); | |||||
last_write_time(p, new_time, ec); | last_write_time(p, new_time, ec); | ||||
file_time_type tt = last_write_time(p); | file_time_type tt = last_write_time(p); | ||||
if (TimeIsRepresentableByFilesystem(new_time)) { | if (TimeIsRepresentableByFilesystem(new_time)) { | ||||
TEST_CHECK(!ec); | TEST_CHECK(!ec); | ||||
TEST_CHECK(tt >= new_time); | TEST_CHECK(CompareTime(tt, new_time)); | ||||
TEST_CHECK(tt < new_time + Sec(1)); | |||||
ec = GetTestEC(); | |||||
last_write_time(p, Clock::now()); | |||||
new_time = file_time_type::min() + MicroSec(1); | last_write_time(p, old_time); | ||||
new_time = file_time_type::min() + SubSec(1); | |||||
ec = GetTestEC(); | |||||
last_write_time(p, new_time, ec); | last_write_time(p, new_time, ec); | ||||
tt = last_write_time(p); | tt = last_write_time(p); | ||||
if (TimeIsRepresentableByFilesystem(new_time)) { | if (TimeIsRepresentableByFilesystem(new_time)) { | ||||
TEST_CHECK(!ec); | TEST_CHECK(!ec); | ||||
TEST_CHECK(tt >= new_time); | TEST_CHECK(CompareTime(tt, new_time)); | ||||
TEST_CHECK(tt < new_time + Sec(1)); | } else { | ||||
TEST_CHECK(ErrorIs(ec, std::errc::value_too_large)); | |||||
TEST_CHECK(tt == old_time); | |||||
} | } | ||||
} else { | |||||
TEST_CHECK(ErrorIs(ec, std::errc::value_too_large)); | |||||
TEST_CHECK(tt == old_time); | |||||
} | } | ||||
} | } | ||||
TEST_CASE(test_write_max_time) { | |||||
TEST_CASE(test_write_min_max_time) | |||||
{ | |||||
using Clock = file_time_type::clock; | using Clock = file_time_type::clock; | ||||
using Sec = std::chrono::seconds; | using Sec = std::chrono::seconds; | ||||
using Hours = std::chrono::hours; | using Hours = std::chrono::hours; | ||||
using Lim = std::numeric_limits<std::time_t>; | |||||
scoped_test_env env; | scoped_test_env env; | ||||
const path p = env.create_file("file", 42); | const path p = env.create_file("file", 42); | ||||
const file_time_type old_time = last_write_time(p); | |||||
std::error_code ec = GetTestEC(); | |||||
file_time_type new_time = file_time_type::max(); | file_time_type new_time = file_time_type::max(); | ||||
ec = GetTestEC(); | std::error_code ec = GetTestEC(); | ||||
last_write_time(p, new_time, ec); | last_write_time(p, new_time, ec); | ||||
file_time_type tt = last_write_time(p); | file_time_type tt = last_write_time(p); | ||||
if (TimeIsRepresentableByFilesystem(new_time)) { | if (TimeIsRepresentableByFilesystem(new_time)) { | ||||
TEST_CHECK(!ec); | TEST_CHECK(!ec); | ||||
TEST_CHECK(tt > new_time - Sec(1)); | TEST_CHECK(CompareTime(tt, new_time)); | ||||
TEST_CHECK(tt <= new_time); | } else { | ||||
TEST_CHECK(ErrorIs(ec, std::errc::value_too_large)); | |||||
TEST_CHECK(tt == old_time); | |||||
} | } | ||||
} | } | ||||
TEST_CASE(test_value_on_failure) | TEST_CASE(test_value_on_failure) | ||||
{ | { | ||||
const path p = StaticEnv::DNE; | const path p = StaticEnv::DNE; | ||||
std::error_code ec = GetTestEC(); | std::error_code ec = GetTestEC(); | ||||
TEST_CHECK(last_write_time(p, ec) == file_time_type::min()); | TEST_CHECK(last_write_time(p, ec) == file_time_type::min()); | ||||
TEST_CHECK(ec); | TEST_CHECK(ErrorIs(ec, std::errc::no_such_file_or_directory)); | ||||
TEST_CHECK(ec != GetTestEC()); | |||||
} | } | ||||
TEST_CASE(test_exists_fails) | TEST_CASE(test_exists_fails) | ||||
{ | { | ||||
scoped_test_env env; | scoped_test_env env; | ||||
const path dir = env.create_dir("dir"); | const path dir = env.create_dir("dir"); | ||||
const path file = env.create_file("dir/file", 42); | const path file = env.create_file("dir/file", 42); | ||||
permissions(dir, perms::none); | permissions(dir, perms::none); | ||||
std::error_code ec = GetTestEC(); | std::error_code ec = GetTestEC(); | ||||
TEST_CHECK(last_write_time(file, ec) == file_time_type::min()); | TEST_CHECK(last_write_time(file, ec) == file_time_type::min()); | ||||
TEST_CHECK(ec); | TEST_CHECK(ErrorIs(ec, std::errc::permission_denied)); | ||||
TEST_CHECK(ec != GetTestEC()); | |||||
ExceptionChecker Checker(file, std::errc::permission_denied, | |||||
"last_write_time"); | |||||
TEST_CHECK_THROW_RESULT(filesystem_error, Checker, last_write_time(file)); | |||||
} | |||||
TEST_CHECK_THROW(filesystem_error, last_write_time(file)); | TEST_CASE(my_test) { | ||||
scoped_test_env env; | |||||
const path p = env.create_file("file", 42); | |||||
using namespace std::chrono; | |||||
using TimeSpec = struct ::timespec; | |||||
TimeSpec ts[2]; | |||||
ts[0].tv_sec = 0; | |||||
ts[0].tv_nsec = UTIME_OMIT; | |||||
ts[1].tv_sec = -1; | |||||
ts[1].tv_nsec = | |||||
duration_cast<nanoseconds>(seconds(1) - nanoseconds(13)).count(); | |||||
if (::utimensat(AT_FDCWD, p.c_str(), ts, 0) == -1) { | |||||
TEST_CHECK(false); | |||||
} | |||||
TimeSpec new_ts = LastWriteTime(p); | |||||
TEST_CHECK(ts[1].tv_sec == new_ts.tv_sec); | |||||
TEST_CHECK(ts[1].tv_nsec == new_ts.tv_nsec); | |||||
} | } | ||||
EricWF: TODO: remove this. | |||||
TEST_SUITE_END() | TEST_SUITE_END() |
TODO: remove this.