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

source code of libcxx/test/std/input.output/filesystems/fs.op.funcs/fs.op.last_write_time/last_write_time.pass.cpp