1 //===----------------------------------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 // UNSUPPORTED: c++03
10 
11 // XFAIL: LIBCXX-AIX-FIXME
12 
13 // The string reported on errors changed, which makes those tests fail when run
14 // against already-released libc++'s.
15 // XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx{{10.15|11.0}}
16 
17 // <filesystem>
18 
19 // file_time_type last_write_time(const path& p);
20 // file_time_type last_write_time(const path& p, std::error_code& ec) noexcept;
21 // void last_write_time(const path& p, file_time_type new_time);
22 // void last_write_time(const path& p, file_time_type new_type,
23 //                      std::error_code& ec) noexcept;
24 
25 #include "filesystem_include.h"
26 #include <chrono>
27 #include <cstdio>
28 #include <cstdlib>
29 #include <ctime>
30 #include <ratio>
31 #include <type_traits>
32 
33 #include "test_macros.h"
34 #include "rapid-cxx-test.h"
35 #include "filesystem_test_helper.h"
36 
37 #include <fcntl.h>
38 #ifdef _WIN32
39 #include <windows.h>
40 #else
41 #include <sys/time.h>
42 #include <sys/stat.h>
43 #endif
44 
45 using namespace fs;
46 
47 using Sec = std::chrono::duration<file_time_type::rep>;
48 using Hours = std::chrono::hours;
49 using Minutes = std::chrono::minutes;
50 using MilliSec = std::chrono::duration<file_time_type::rep, std::milli>;
51 using MicroSec = std::chrono::duration<file_time_type::rep, std::micro>;
52 using NanoSec = std::chrono::duration<file_time_type::rep, std::nano>;
53 using std::chrono::duration_cast;
54 
55 #ifdef _WIN32
56 struct TimeSpec {
57   int64_t tv_sec;
58   int64_t tv_nsec;
59 };
60 struct StatT {
61   TimeSpec st_atim;
62   TimeSpec st_mtim;
63 };
64 // There were 369 years and 89 leap days from the Windows epoch
65 // (1601) to the Unix epoch (1970).
66 #define FILE_TIME_OFFSET_SECS (uint64_t(369 * 365 + 89) * (24 * 60 * 60))
filetime_to_timespec(LARGE_INTEGER li)67 static TimeSpec filetime_to_timespec(LARGE_INTEGER li) {
68   TimeSpec ret;
69   ret.tv_sec = li.QuadPart / 10000000 - FILE_TIME_OFFSET_SECS;
70   ret.tv_nsec = (li.QuadPart % 10000000) * 100;
71   return ret;
72 }
stat_file(const char * path,StatT * buf,int flags)73 static int stat_file(const char *path, StatT *buf, int flags) {
74   HANDLE h = CreateFileA(path, FILE_READ_ATTRIBUTES,
75                          FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
76                          nullptr, OPEN_EXISTING,
77                          FILE_FLAG_BACKUP_SEMANTICS | flags, nullptr);
78   if (h == INVALID_HANDLE_VALUE)
79     return -1;
80   int ret = -1;
81   FILE_BASIC_INFO basic;
82   if (GetFileInformationByHandleEx(h, FileBasicInfo, &basic, sizeof(basic))) {
83     buf->st_mtim = filetime_to_timespec(basic.LastWriteTime);
84     buf->st_atim = filetime_to_timespec(basic.LastAccessTime);
85     ret = 0;
86   }
87   CloseHandle(h);
88   return ret;
89 }
stat(const char * path,StatT * buf)90 static int stat(const char *path, StatT *buf) {
91   return stat_file(path, buf, 0);
92 }
lstat(const char * path,StatT * buf)93 static int lstat(const char *path, StatT *buf) {
94   return stat_file(path, buf, FILE_FLAG_OPEN_REPARSE_POINT);
95 }
96 #else
97 using TimeSpec = timespec;
98 using StatT = struct stat;
99 #endif
100 
101 #if defined(__APPLE__)
extract_mtime(StatT const & st)102 TimeSpec extract_mtime(StatT const& st) { return st.st_mtimespec; }
extract_atime(StatT const & st)103 TimeSpec extract_atime(StatT const& st) { return st.st_atimespec; }
104 #else
extract_mtime(StatT const & st)105 TimeSpec extract_mtime(StatT const& st) { return st.st_mtim; }
extract_atime(StatT const & st)106 TimeSpec extract_atime(StatT const& st) { return st.st_atim; }
107 #endif
108 
ConvertToTimeSpec(TimeSpec & ts,file_time_type ft)109 bool ConvertToTimeSpec(TimeSpec& ts, file_time_type ft) {
110   using SecFieldT = decltype(TimeSpec::tv_sec);
111   using NSecFieldT = decltype(TimeSpec::tv_nsec);
112   using SecLim = std::numeric_limits<SecFieldT>;
113   using NSecLim = std::numeric_limits<NSecFieldT>;
114 
115   auto secs = duration_cast<Sec>(ft.time_since_epoch());
116   auto nsecs = duration_cast<NanoSec>(ft.time_since_epoch() - secs);
117   if (nsecs.count() < 0) {
118     if (Sec::min().count() > SecLim::min()) {
119       secs += Sec(1);
120       nsecs -= Sec(1);
121     } else {
122       nsecs = NanoSec(0);
123     }
124   }
125   if (SecLim::max() < secs.count() || SecLim::min() > secs.count())
126     return false;
127   if (NSecLim::max() < nsecs.count() || NSecLim::min() > nsecs.count())
128     return false;
129   ts.tv_sec = secs.count();
130   ts.tv_nsec = nsecs.count();
131   return true;
132 }
133 
ConvertFromTimeSpec(file_time_type & ft,TimeSpec ts)134 bool ConvertFromTimeSpec(file_time_type& ft, TimeSpec ts) {
135   auto secs_part = duration_cast<file_time_type::duration>(Sec(ts.tv_sec));
136   if (duration_cast<Sec>(secs_part).count() != ts.tv_sec)
137     return false;
138   auto subsecs = duration_cast<file_time_type::duration>(NanoSec(ts.tv_nsec));
139   auto dur = secs_part + subsecs;
140   if (dur < secs_part && subsecs.count() >= 0)
141     return false;
142   ft = file_time_type(dur);
143   return true;
144 }
145 
CompareTimeExact(TimeSpec ts,TimeSpec ts2)146 bool CompareTimeExact(TimeSpec ts, TimeSpec ts2) {
147   return ts2.tv_sec == ts.tv_sec && ts2.tv_nsec == ts.tv_nsec;
148 }
CompareTimeExact(file_time_type ft,TimeSpec ts)149 bool CompareTimeExact(file_time_type ft, TimeSpec ts) {
150   TimeSpec ts2 = {};
151   if (!ConvertToTimeSpec(ts2, ft))
152     return false;
153   return CompareTimeExact(ts, ts2);
154 }
CompareTimeExact(TimeSpec ts,file_time_type ft)155 bool CompareTimeExact(TimeSpec ts, file_time_type ft) {
156   return CompareTimeExact(ft, ts);
157 }
158 
159 struct Times {
160   TimeSpec access, write;
161 };
162 
GetTimes(path const & p)163 Times GetTimes(path const& p) {
164     StatT st;
165     if (::stat(p.string().c_str(), &st) == -1) {
166         std::error_code ec(errno, std::generic_category());
167 #ifndef TEST_HAS_NO_EXCEPTIONS
168         throw ec;
169 #else
170         std::exit(EXIT_FAILURE);
171 #endif
172     }
173     return {extract_atime(st), extract_mtime(st)};
174 }
175 
LastAccessTime(path const & p)176 TimeSpec LastAccessTime(path const& p) { return GetTimes(p).access; }
177 
LastWriteTime(path const & p)178 TimeSpec LastWriteTime(path const& p) { return GetTimes(p).write; }
179 
GetSymlinkTimes(path const & p)180 Times GetSymlinkTimes(path const& p) {
181   StatT st;
182   if (::lstat(p.string().c_str(), &st) == -1) {
183     std::error_code ec(errno, std::generic_category());
184 #ifndef TEST_HAS_NO_EXCEPTIONS
185         throw ec;
186 #else
187         std::exit(EXIT_FAILURE);
188 #endif
189     }
190     Times res;
191     res.access = extract_atime(st);
192     res.write = extract_mtime(st);
193     return res;
194 }
195 
196 namespace {
197 
198 // In some configurations, the comparison is tautological and the test is valid.
199 // We disable the warning so that we can actually test it regardless.
200 TEST_DIAGNOSTIC_PUSH
201 TEST_CLANG_DIAGNOSTIC_IGNORED("-Wtautological-constant-compare")
202 
__anonc8913b790202null203 static const bool SupportsNegativeTimes = [] {
204   using namespace std::chrono;
205   std::error_code ec;
206   TimeSpec old_write_time, new_write_time;
207   { // WARNING: Do not assert in this scope.
208     scoped_test_env env;
209     const path file = env.create_file("file", 42);
210     old_write_time = LastWriteTime(file);
211     file_time_type tp(seconds(-5));
212     fs::last_write_time(file, tp, ec);
213     new_write_time = LastWriteTime(file);
214   }
215 
216   return !ec && new_write_time.tv_sec < 0;
217 }();
218 
__anonc8913b790302null219 static const bool SupportsMaxTime = [] {
220   using namespace std::chrono;
221   TimeSpec max_ts = {};
222   if (!ConvertToTimeSpec(max_ts, file_time_type::max()))
223     return false;
224 
225   std::error_code ec;
226   TimeSpec old_write_time, new_write_time;
227   { // WARNING: Do not assert in this scope.
228     scoped_test_env env;
229     const path file = env.create_file("file", 42);
230     old_write_time = LastWriteTime(file);
231     file_time_type tp = file_time_type::max();
232     fs::last_write_time(file, tp, ec);
233     new_write_time = LastWriteTime(file);
234   }
235   return !ec && new_write_time.tv_sec > max_ts.tv_sec - 1;
236 }();
237 
__anonc8913b790402null238 static const bool SupportsMinTime = [] {
239   using namespace std::chrono;
240   TimeSpec min_ts = {};
241   if (!ConvertToTimeSpec(min_ts, file_time_type::min()))
242     return false;
243   std::error_code ec;
244   TimeSpec old_write_time, new_write_time;
245   { // WARNING: Do not assert in this scope.
246     scoped_test_env env;
247     const path file = env.create_file("file", 42);
248     old_write_time = LastWriteTime(file);
249     file_time_type tp = file_time_type::min();
250     fs::last_write_time(file, tp, ec);
251     new_write_time = LastWriteTime(file);
252   }
253   return !ec && new_write_time.tv_sec < min_ts.tv_sec + 1;
254 }();
255 
__anonc8913b790502null256 static const bool SupportsNanosecondRoundTrip = [] {
257   NanoSec ns(3);
258   static_assert(std::is_same<file_time_type::period, std::nano>::value, "");
259 
260   // Test that the system call we use to set the times also supports nanosecond
261   // resolution. (utimes does not)
262   file_time_type ft(ns);
263   {
264     scoped_test_env env;
265     const path p = env.create_file("file", 42);
266     last_write_time(p, ft);
267     return last_write_time(p) == ft;
268   }
269 }();
270 
271 // The HFS+ filesystem (used by default before macOS 10.13) stores timestamps at
272 // a 1-second granularity, and APFS (now the default) at a 1 nanosecond granularity.
273 // 1-second granularity is also the norm on many of the supported filesystems
274 // on Linux as well.
__anonc8913b790602null275 static const bool WorkaroundStatTruncatesToSeconds = [] {
276   MicroSec micros(3);
277   static_assert(std::is_same<file_time_type::period, std::nano>::value, "");
278 
279   file_time_type ft(micros);
280   {
281     scoped_test_env env;
282     const path p = env.create_file("file", 42);
283     if (LastWriteTime(p).tv_nsec != 0)
284       return false;
285     last_write_time(p, ft);
286     return last_write_time(p) != ft && LastWriteTime(p).tv_nsec == 0;
287   }
288 }();
289 
__anonc8913b790702null290 static const bool SupportsMinRoundTrip = [] {
291   TimeSpec ts = {};
292   if (!ConvertToTimeSpec(ts, file_time_type::min()))
293     return false;
294   file_time_type min_val = {};
295   if (!ConvertFromTimeSpec(min_val, ts))
296     return false;
297   return min_val == file_time_type::min();
298 }();
299 
300 } // end namespace
301 
CompareTime(TimeSpec t1,TimeSpec t2)302 static bool CompareTime(TimeSpec t1, TimeSpec t2) {
303   if (SupportsNanosecondRoundTrip)
304     return CompareTimeExact(t1, t2);
305   if (t1.tv_sec != t2.tv_sec)
306     return false;
307 
308   auto diff = std::abs(t1.tv_nsec - t2.tv_nsec);
309   if (WorkaroundStatTruncatesToSeconds)
310    return diff < duration_cast<NanoSec>(Sec(1)).count();
311   return diff < duration_cast<NanoSec>(MicroSec(1)).count();
312 }
313 
CompareTime(file_time_type t1,TimeSpec t2)314 static bool CompareTime(file_time_type t1, TimeSpec t2) {
315   TimeSpec ts1 = {};
316   if (!ConvertToTimeSpec(ts1, t1))
317     return false;
318   return CompareTime(ts1, t2);
319 }
320 
CompareTime(TimeSpec t1,file_time_type t2)321 static bool CompareTime(TimeSpec t1, file_time_type t2) {
322   return CompareTime(t2, t1);
323 }
324 
CompareTime(file_time_type t1,file_time_type t2)325 static bool CompareTime(file_time_type t1, file_time_type t2) {
326   auto min_secs = duration_cast<Sec>(file_time_type::min().time_since_epoch());
327   bool IsMin =
328       t1.time_since_epoch() < min_secs || t2.time_since_epoch() < min_secs;
329 
330   if (SupportsNanosecondRoundTrip && (!IsMin || SupportsMinRoundTrip))
331     return t1 == t2;
332   if (IsMin) {
333     return duration_cast<Sec>(t1.time_since_epoch()) ==
334            duration_cast<Sec>(t2.time_since_epoch());
335   }
336   file_time_type::duration dur;
337   if (t1 > t2)
338     dur = t1 - t2;
339   else
340     dur = t2 - t1;
341   if (WorkaroundStatTruncatesToSeconds)
342     return duration_cast<Sec>(dur).count() == 0;
343   return duration_cast<MicroSec>(dur).count() == 0;
344 }
345 
346 // Check if a time point is representable on a given filesystem. Check that:
347 // (A) 'tp' is representable as a time_t
348 // (B) 'tp' is non-negative or the filesystem supports negative times.
349 // (C) 'tp' is not 'file_time_type::max()' or the filesystem supports the max
350 //     value.
351 // (D) 'tp' is not 'file_time_type::min()' or the filesystem supports the min
352 //     value.
TimeIsRepresentableByFilesystem(file_time_type tp)353 inline bool TimeIsRepresentableByFilesystem(file_time_type tp) {
354   TimeSpec ts = {};
355   if (!ConvertToTimeSpec(ts, tp))
356     return false;
357   else if (tp.time_since_epoch().count() < 0 && !SupportsNegativeTimes)
358     return false;
359   else if (tp == file_time_type::max() && !SupportsMaxTime)
360     return false;
361   else if (tp == file_time_type::min() && !SupportsMinTime)
362     return false;
363   return true;
364 }
365 
366 TEST_DIAGNOSTIC_POP
367 
368 // Create a sub-second duration using the smallest period the filesystem supports.
SubSec(long long val)369 file_time_type::duration SubSec(long long val) {
370   using SubSecT = file_time_type::duration;
371   if (SupportsNanosecondRoundTrip) {
372     return duration_cast<SubSecT>(NanoSec(val));
373   } else {
374     return duration_cast<SubSecT>(MicroSec(val));
375   }
376 }
377 
378 TEST_SUITE(last_write_time_test_suite)
379 
TEST_CASE(signature_test)380 TEST_CASE(signature_test)
381 {
382     const file_time_type t;
383     const path p; ((void)p);
384     std::error_code ec; ((void)ec);
385     ASSERT_SAME_TYPE(decltype(last_write_time(p)), file_time_type);
386     ASSERT_SAME_TYPE(decltype(last_write_time(p, ec)), file_time_type);
387     ASSERT_SAME_TYPE(decltype(last_write_time(p, t)), void);
388     ASSERT_SAME_TYPE(decltype(last_write_time(p, t, ec)), void);
389     ASSERT_NOT_NOEXCEPT(last_write_time(p));
390     ASSERT_NOT_NOEXCEPT(last_write_time(p, t));
391     ASSERT_NOEXCEPT(last_write_time(p, ec));
392     ASSERT_NOEXCEPT(last_write_time(p, t, ec));
393 }
394 
TEST_CASE(read_last_write_time_static_env_test)395 TEST_CASE(read_last_write_time_static_env_test)
396 {
397     static_test_env static_env;
398     using C = file_time_type::clock;
399     file_time_type min = file_time_type::min();
400     // Sleep a little to make sure that static_env.File created above is
401     // strictly older than C::now() even with a coarser clock granularity
402     // in C::now(). (GetSystemTimeAsFileTime on windows has a fairly coarse
403     // granularity.)
404     SleepFor(MilliSec(30));
405     {
406         file_time_type ret = last_write_time(static_env.File);
407         TEST_CHECK(ret != min);
408         TEST_CHECK(ret < C::now());
409         TEST_CHECK(CompareTime(ret, LastWriteTime(static_env.File)));
410 
411         file_time_type ret2 = last_write_time(static_env.SymlinkToFile);
412         TEST_CHECK(CompareTime(ret, ret2));
413         TEST_CHECK(CompareTime(ret2, LastWriteTime(static_env.SymlinkToFile)));
414     }
415     {
416         file_time_type ret = last_write_time(static_env.Dir);
417         TEST_CHECK(ret != min);
418         TEST_CHECK(ret < C::now());
419         TEST_CHECK(CompareTime(ret, LastWriteTime(static_env.Dir)));
420 
421         file_time_type ret2 = last_write_time(static_env.SymlinkToDir);
422         TEST_CHECK(CompareTime(ret, ret2));
423         TEST_CHECK(CompareTime(ret2, LastWriteTime(static_env.SymlinkToDir)));
424     }
425 }
426 
TEST_CASE(get_last_write_time_dynamic_env_test)427 TEST_CASE(get_last_write_time_dynamic_env_test)
428 {
429     using Sec = std::chrono::seconds;
430     scoped_test_env env;
431 
432     const path file = env.create_file("file", 42);
433     const path dir = env.create_dir("dir");
434 
435     const auto file_times = GetTimes(file);
436     const TimeSpec file_write_time = file_times.write;
437     const auto dir_times = GetTimes(dir);
438     const TimeSpec dir_write_time = dir_times.write;
439 
440     file_time_type ftime = last_write_time(file);
441     TEST_CHECK(CompareTime(ftime, file_write_time));
442 
443     file_time_type dtime = last_write_time(dir);
444     TEST_CHECK(CompareTime(dtime, dir_write_time));
445 
446     SleepFor(Sec(2));
447 
448     // update file and add a file to the directory. Make sure the times increase.
449     std::FILE* of = std::fopen(file.string().c_str(), "a");
450     std::fwrite("hello", 1, sizeof("hello"), of);
451     std::fclose(of);
452     env.create_file("dir/file1", 1);
453 
454     file_time_type ftime2 = last_write_time(file);
455     file_time_type dtime2 = last_write_time(dir);
456 
457     TEST_CHECK(ftime2 > ftime);
458     TEST_CHECK(dtime2 > dtime);
459     TEST_CHECK(CompareTime(LastWriteTime(file), ftime2));
460     TEST_CHECK(CompareTime(LastWriteTime(dir), dtime2));
461 }
462 
463 
TEST_CASE(set_last_write_time_dynamic_env_test)464 TEST_CASE(set_last_write_time_dynamic_env_test)
465 {
466     using Clock = file_time_type::clock;
467     scoped_test_env env;
468 
469     const path file = env.create_file("file", 42);
470     const path dir = env.create_dir("dir");
471     const auto now = Clock::now();
472     const file_time_type epoch_time = now - now.time_since_epoch();
473 
474     const file_time_type future_time = now + Hours(3) + Sec(42) + SubSec(17);
475     const file_time_type past_time = now - Minutes(3) - Sec(42) - SubSec(17);
476     const file_time_type before_epoch_time =
477         epoch_time - Minutes(3) - Sec(42) - SubSec(17);
478     // FreeBSD has a bug in their utimes implementation where the time is not update
479     // when the number of seconds is '-1'.
480 #if defined(__FreeBSD__) || defined(__NetBSD__)
481     const file_time_type just_before_epoch_time =
482         epoch_time - Sec(2) - SubSec(17);
483 #else
484     const file_time_type just_before_epoch_time = epoch_time - SubSec(17);
485 #endif
486 
487     struct TestCase {
488       const char * case_name;
489       path p;
490       file_time_type new_time;
491     } cases[] = {
492         {"file, epoch_time", file, epoch_time},
493         {"dir, epoch_time", dir, epoch_time},
494         {"file, future_time", file, future_time},
495         {"dir, future_time", dir, future_time},
496         {"file, past_time", file, past_time},
497         {"dir, past_time", dir, past_time},
498         {"file, before_epoch_time", file, before_epoch_time},
499         {"dir, before_epoch_time", dir, before_epoch_time},
500         {"file, just_before_epoch_time", file, just_before_epoch_time},
501         {"dir, just_before_epoch_time", dir, just_before_epoch_time}
502     };
503     for (const auto& TC : cases) {
504         const auto old_times = GetTimes(TC.p);
505         file_time_type old_time;
506         TEST_REQUIRE(ConvertFromTimeSpec(old_time, old_times.write));
507 
508         std::error_code ec = GetTestEC();
509         last_write_time(TC.p, TC.new_time, ec);
510         TEST_CHECK(!ec);
511 
512         ec = GetTestEC();
513         file_time_type  got_time = last_write_time(TC.p, ec);
514         TEST_REQUIRE(!ec);
515 
516         if (TimeIsRepresentableByFilesystem(TC.new_time)) {
517             TEST_CHECK(got_time != old_time);
518             TEST_CHECK(CompareTime(got_time, TC.new_time));
519             TEST_CHECK(CompareTime(LastAccessTime(TC.p), old_times.access));
520         }
521     }
522 }
523 
TEST_CASE(last_write_time_symlink_test)524 TEST_CASE(last_write_time_symlink_test)
525 {
526     using Clock = file_time_type::clock;
527 
528     scoped_test_env env;
529 
530     const path file = env.create_file("file", 42);
531     const path sym = env.create_symlink("file", "sym");
532 
533     const file_time_type new_time = Clock::now() + Hours(3);
534 
535     const auto old_times = GetTimes(sym);
536     const auto old_sym_times = GetSymlinkTimes(sym);
537 
538     std::error_code ec = GetTestEC();
539     last_write_time(sym, new_time, ec);
540     TEST_CHECK(!ec);
541 
542     file_time_type  got_time = last_write_time(sym);
543     TEST_CHECK(!CompareTime(got_time, old_times.write));
544     if (!WorkaroundStatTruncatesToSeconds) {
545       TEST_CHECK(got_time == new_time);
546     } else {
547       TEST_CHECK(CompareTime(got_time, new_time));
548     }
549 
550     TEST_CHECK(CompareTime(LastWriteTime(file), new_time));
551     TEST_CHECK(CompareTime(LastAccessTime(sym), old_times.access));
552     Times sym_times = GetSymlinkTimes(sym);
553     TEST_CHECK(CompareTime(sym_times.write, old_sym_times.write));
554 }
555 
556 
TEST_CASE(test_write_min_time)557 TEST_CASE(test_write_min_time)
558 {
559     scoped_test_env env;
560     const path p = env.create_file("file", 42);
561     const file_time_type old_time = last_write_time(p);
562     file_time_type new_time = file_time_type::min();
563 
564     std::error_code ec = GetTestEC();
565     last_write_time(p, new_time, ec);
566     file_time_type tt = last_write_time(p);
567 
568     if (TimeIsRepresentableByFilesystem(new_time)) {
569         TEST_CHECK(!ec);
570         TEST_CHECK(CompareTime(tt, new_time));
571 
572         last_write_time(p, old_time);
573         new_time = file_time_type::min() + SubSec(1);
574 
575         ec = GetTestEC();
576         last_write_time(p, new_time, ec);
577         tt = last_write_time(p);
578 
579         if (TimeIsRepresentableByFilesystem(new_time)) {
580             TEST_CHECK(!ec);
581             TEST_CHECK(CompareTime(tt, new_time));
582         } else {
583           TEST_CHECK(ErrorIs(ec, std::errc::value_too_large));
584           TEST_CHECK(tt == old_time);
585         }
586     } else {
587       TEST_CHECK(ErrorIs(ec, std::errc::value_too_large));
588       TEST_CHECK(tt == old_time);
589     }
590 }
591 
TEST_CASE(test_write_max_time)592 TEST_CASE(test_write_max_time) {
593   scoped_test_env env;
594   const path p = env.create_file("file", 42);
595   const file_time_type old_time = last_write_time(p);
596   file_time_type new_time = file_time_type::max();
597 
598   std::error_code ec = GetTestEC();
599   last_write_time(p, new_time, ec);
600   file_time_type tt = last_write_time(p);
601 
602   if (TimeIsRepresentableByFilesystem(new_time)) {
603     TEST_CHECK(!ec);
604     TEST_CHECK(CompareTime(tt, new_time));
605   } else {
606     TEST_CHECK(ErrorIs(ec, std::errc::value_too_large));
607     TEST_CHECK(tt == old_time);
608   }
609 }
610 
TEST_CASE(test_value_on_failure)611 TEST_CASE(test_value_on_failure)
612 {
613     static_test_env static_env;
614     const path p = static_env.DNE;
615     std::error_code ec = GetTestEC();
616     TEST_CHECK(last_write_time(p, ec) == file_time_type::min());
617     TEST_CHECK(ErrorIs(ec, std::errc::no_such_file_or_directory));
618 }
619 
620 // Windows doesn't support setting perms::none to trigger failures
621 // reading directories.
622 #ifndef TEST_WIN_NO_FILESYSTEM_PERMS_NONE
TEST_CASE(test_exists_fails)623 TEST_CASE(test_exists_fails)
624 {
625     scoped_test_env env;
626     const path dir = env.create_dir("dir");
627     const path file = env.create_file("dir/file", 42);
628     permissions(dir, perms::none);
629 
630     std::error_code ec = GetTestEC();
631     TEST_CHECK(last_write_time(file, ec) == file_time_type::min());
632     TEST_CHECK(ErrorIs(ec, std::errc::permission_denied));
633 
634     ExceptionChecker Checker(file, std::errc::permission_denied,
635                              "last_write_time");
636     TEST_CHECK_THROW_RESULT(filesystem_error, Checker, last_write_time(file));
637 }
638 #endif
639 
640 TEST_SUITE_END()
641