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