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 44 namespace fs = std::filesystem; 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 std::int64_t tv_sec; 58 std::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 (std::uint64_t(369 * 365 + 89) * (24 * 60 * 60)) 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 } 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 } 90 static int stat(const char *path, StatT *buf) { 91 return stat_file(path, buf, 0); 92 } 93 static int lstat(const char *path, StatT *buf) { 94 return stat_file(path, buf, FILE_FLAG_OPEN_REPARSE_POINT); 95 } 96 #elif defined(_AIX) 97 using TimeSpec = st_timespec_t; 98 using StatT = struct stat; 99 #else 100 using TimeSpec = timespec; 101 using StatT = struct stat; 102 #endif 103 104 #if defined(__APPLE__) 105 TimeSpec extract_mtime(StatT const& st) { return st.st_mtimespec; } 106 TimeSpec extract_atime(StatT const& st) { return st.st_atimespec; } 107 #else 108 TimeSpec extract_mtime(StatT const& st) { return st.st_mtim; } 109 TimeSpec extract_atime(StatT const& st) { return st.st_atim; } 110 #endif 111 112 bool 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>(ft.time_since_epoch()); 119 auto nsecs = duration_cast<NanoSec>(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 137 bool ConvertFromTimeSpec(file_time_type& ft, TimeSpec ts) { 138 auto secs_part = duration_cast<file_time_type::duration>(Sec(ts.tv_sec)); 139 if (duration_cast<Sec>(secs_part).count() != ts.tv_sec) 140 return false; 141 auto subsecs = duration_cast<file_time_type::duration>(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 149 bool CompareTimeExact(TimeSpec ts, TimeSpec ts2) { 150 return ts2.tv_sec == ts.tv_sec && ts2.tv_nsec == ts.tv_nsec; 151 } 152 bool CompareTimeExact(file_time_type ft, TimeSpec ts) { 153 TimeSpec ts2 = {}; 154 if (!ConvertToTimeSpec(ts2, ft)) 155 return false; 156 return CompareTimeExact(ts, ts2); 157 } 158 bool CompareTimeExact(TimeSpec ts, file_time_type ft) { 159 return CompareTimeExact(ft, ts); 160 } 161 162 struct Times { 163 TimeSpec access, write; 164 }; 165 166 Times GetTimes(path const& p) { 167 StatT st; 168 if (::stat(p.string().c_str(), &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 {extract_atime(st), extract_mtime(st)}; 177 } 178 179 TimeSpec LastAccessTime(path const& p) { return GetTimes(p).access; } 180 181 TimeSpec LastWriteTime(path const& p) { return GetTimes(p).write; } 182 183 Times GetSymlinkTimes(path const& p) { 184 StatT st; 185 if (::lstat(p.string().c_str(), &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 199 namespace { 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. 203 TEST_DIAGNOSTIC_PUSH 204 TEST_CLANG_DIAGNOSTIC_IGNORED("-Wtautological-constant-compare") 205 206 static 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(file); 214 file_time_type tp(seconds(-5)); 215 fs::last_write_time(file, tp, ec); 216 new_write_time = LastWriteTime(file); 217 } 218 219 return !ec && new_write_time.tv_sec < 0; 220 }(); 221 222 static const bool SupportsMaxTime = [] { 223 using namespace std::chrono; 224 TimeSpec max_ts = {}; 225 if (!ConvertToTimeSpec(max_ts, 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(file); 234 file_time_type tp = file_time_type::max(); 235 fs::last_write_time(file, tp, ec); 236 new_write_time = LastWriteTime(file); 237 } 238 return !ec && new_write_time.tv_sec > max_ts.tv_sec - 1; 239 }(); 240 241 static const bool SupportsMinTime = [] { 242 using namespace std::chrono; 243 TimeSpec min_ts = {}; 244 if (!ConvertToTimeSpec(min_ts, 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(file); 252 file_time_type tp = file_time_type::min(); 253 fs::last_write_time(file, tp, ec); 254 new_write_time = LastWriteTime(file); 255 } 256 return !ec && new_write_time.tv_sec < min_ts.tv_sec + 1; 257 }(); 258 259 static 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, 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. 278 static 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, ft); 289 return last_write_time(p) != ft && LastWriteTime(p).tv_nsec == 0; 290 } 291 }(); 292 293 static const bool SupportsMinRoundTrip = [] { 294 TimeSpec ts = {}; 295 if (!ConvertToTimeSpec(ts, file_time_type::min())) 296 return false; 297 file_time_type min_val = {}; 298 if (!ConvertFromTimeSpec(min_val, ts)) 299 return false; 300 return min_val == file_time_type::min(); 301 }(); 302 303 } // namespace 304 305 static bool CompareTime(TimeSpec t1, TimeSpec t2) { 306 if (SupportsNanosecondRoundTrip) 307 return CompareTimeExact(t1, t2); 308 if (t1.tv_sec != t2.tv_sec) 309 return false; 310 311 auto diff = std::abs(t1.tv_nsec - t2.tv_nsec); 312 if (WorkaroundStatTruncatesToSeconds) 313 return diff < duration_cast<NanoSec>(Sec(1)).count(); 314 return diff < duration_cast<NanoSec>(MicroSec(1)).count(); 315 } 316 317 static bool CompareTime(file_time_type t1, TimeSpec t2) { 318 TimeSpec ts1 = {}; 319 if (!ConvertToTimeSpec(ts1, t1)) 320 return false; 321 return CompareTime(ts1, t2); 322 } 323 324 static bool CompareTime(TimeSpec t1, file_time_type t2) { 325 return CompareTime(t2, t1); 326 } 327 328 static bool CompareTime(file_time_type t1, file_time_type t2) { 329 auto min_secs = duration_cast<Sec>(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>(t1.time_since_epoch()) == 337 duration_cast<Sec>(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>(dur).count() == 0; 346 return duration_cast<MicroSec>(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. 356 inline bool TimeIsRepresentableByFilesystem(file_time_type tp) { 357 TimeSpec ts = {}; 358 if (!ConvertToTimeSpec(ts, 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 369 TEST_DIAGNOSTIC_POP 370 371 // Create a sub-second duration using the smallest period the filesystem supports. 372 file_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 381 static 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)); 391 ASSERT_NOT_NOEXCEPT(last_write_time(p, t)); 392 ASSERT_NOEXCEPT(last_write_time(p, ec)); 393 ASSERT_NOEXCEPT(last_write_time(p, t, ec)); 394 } 395 396 static 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(ret, 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(ret, ret2)); 424 assert(CompareTime(ret2, LastWriteTime(static_env.SymlinkToDir))); 425 } 426 } 427 428 static 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(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 assert(CompareTime(ftime, file_write_time)); 442 443 file_time_type dtime = last_write_time(dir); 444 assert(CompareTime(dtime, 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(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 assert(ftime2 > ftime); 458 assert(dtime2 > dtime); 459 assert(CompareTime(LastWriteTime(file), ftime2)); 460 assert(CompareTime(LastWriteTime(dir), dtime2)); 461 } 462 463 464 static 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 {"file, epoch_time", file, epoch_time}, 495 {"dir, epoch_time", dir, epoch_time}, 496 {"file, future_time", file, future_time}, 497 {"dir, future_time", dir, future_time}, 498 {"file, past_time", file, past_time}, 499 {"dir, past_time", dir, 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 {"file, before_epoch_time", file, before_epoch_time}, 506 {"dir, before_epoch_time", dir, before_epoch_time}, 507 {"file, just_before_epoch_time", file, just_before_epoch_time}, 508 {"dir, just_before_epoch_time", dir, 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 533 static 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(sym); 545 const auto old_sym_times = GetSymlinkTimes(sym); 546 547 std::error_code ec = GetTestEC(); 548 last_write_time(sym, new_time, ec); 549 assert(!ec); 550 551 file_time_type got_time = last_write_time(sym); 552 assert(!CompareTime(got_time, old_times.write)); 553 if (!WorkaroundStatTruncatesToSeconds) { 554 assert(got_time == new_time); 555 } else { 556 assert(CompareTime(got_time, new_time)); 557 } 558 559 assert(CompareTime(LastWriteTime(file), new_time)); 560 assert(CompareTime(LastAccessTime(sym), old_times.access)); 561 Times sym_times = GetSymlinkTimes(sym); 562 assert(CompareTime(sym_times.write, old_sym_times.write)); 563 } 564 565 566 static 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); 571 file_time_type new_time = file_time_type::min(); 572 573 std::error_code ec = GetTestEC(); 574 last_write_time(p, new_time, ec); 575 file_time_type tt = last_write_time(p); 576 577 if (TimeIsRepresentableByFilesystem(new_time)) { 578 assert(!ec); 579 assert(CompareTime(tt, new_time)); 580 581 last_write_time(p, old_time); 582 new_time = file_time_type::min() + SubSec(1); 583 584 ec = GetTestEC(); 585 last_write_time(p, new_time, ec); 586 tt = last_write_time(p); 587 588 if (TimeIsRepresentableByFilesystem(new_time)) { 589 assert(!ec); 590 assert(CompareTime(tt, 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 601 static 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); 605 file_time_type new_time = file_time_type::max(); 606 607 std::error_code ec = GetTestEC(); 608 last_write_time(p, new_time, ec); 609 file_time_type tt = last_write_time(p); 610 611 if (TimeIsRepresentableByFilesystem(new_time)) { 612 assert(!ec); 613 assert(CompareTime(tt, new_time)); 614 } else { 615 assert(ErrorIs(ec, std::errc::value_too_large)); 616 assert(tt == old_time); 617 } 618 } 619 620 static 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, 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 632 static 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(dir, perms::none); 638 639 std::error_code ec = GetTestEC(); 640 assert(last_write_time(file, 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 649 int 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