• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //===----------------------------------------------------------------------===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is dual licensed under the MIT and the University of Illinois Open
6 // Source Licenses. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 
10 // UNSUPPORTED: c++98, c++03
11 
12 // <experimental/filesystem>
13 
14 // file_time_type last_write_time(const path& p);
15 // file_time_type last_write_time(const path& p, std::error_code& ec) noexcept;
16 // void last_write_time(const path& p, file_time_type new_time);
17 // void last_write_time(const path& p, file_time_type new_type,
18 //                      std::error_code& ec) noexcept;
19 
20 
21 #include <experimental/filesystem>
22 #include <type_traits>
23 #include <chrono>
24 #include <fstream>
25 #include <cstdlib>
26 
27 #include "test_macros.h"
28 #include "rapid-cxx-test.hpp"
29 #include "filesystem_test_helper.hpp"
30 
31 #include <sys/stat.h>
32 #include <iostream>
33 
34 using namespace std::experimental::filesystem;
35 
36 
GetTimes(path const & p)37 std::pair<std::time_t, std::time_t> GetTimes(path const& p) {
38     using Clock = file_time_type::clock;
39     struct ::stat st;
40     if (::stat(p.c_str(), &st) == -1) {
41         std::error_code ec(errno, std::generic_category());
42 #ifndef TEST_HAS_NO_EXCEPTIONS
43         throw ec;
44 #else
45         std::cerr << ec.message() << std::endl;
46         std::exit(EXIT_FAILURE);
47 #endif
48     }
49     return {st.st_atime, st.st_mtime};
50 }
51 
LastAccessTime(path const & p)52 std::time_t LastAccessTime(path const& p) {
53     return GetTimes(p).first;
54 }
55 
LastWriteTime(path const & p)56 std::time_t LastWriteTime(path const& p) {
57     return GetTimes(p).second;
58 }
59 
GetSymlinkTimes(path const & p)60 std::pair<std::time_t, std::time_t> GetSymlinkTimes(path const& p) {
61     using Clock = file_time_type::clock;
62     struct ::stat st;
63     if (::lstat(p.c_str(), &st) == -1) {
64         std::error_code ec(errno, std::generic_category());
65 #ifndef TEST_HAS_NO_EXCEPTIONS
66         throw ec;
67 #else
68         std::cerr << ec.message() << std::endl;
69         std::exit(EXIT_FAILURE);
70 #endif
71     }
72     return {st.st_atime, st.st_mtime};
73 }
74 
75 namespace {
TestSupportsNegativeTimes()76 bool TestSupportsNegativeTimes() {
77     using namespace std::chrono;
78     std::error_code ec;
79     std::time_t old_write_time, new_write_time;
80     { // WARNING: Do not assert in this scope.
81         scoped_test_env env;
82         const path file = env.create_file("file", 42);
83         old_write_time = LastWriteTime(file);
84         file_time_type tp(seconds(-5));
85         fs::last_write_time(file, tp, ec);
86         new_write_time = LastWriteTime(file);
87     }
88     return !ec && new_write_time <= -5;
89 }
90 
TestSupportsMaxTime()91 bool TestSupportsMaxTime() {
92     using namespace std::chrono;
93     using Lim = std::numeric_limits<std::time_t>;
94     auto max_sec = duration_cast<seconds>(file_time_type::max().time_since_epoch()).count();
95     if (max_sec > Lim::max()) return false;
96     std::error_code ec;
97     std::time_t old_write_time, new_write_time;
98     { // WARNING: Do not assert in this scope.
99         scoped_test_env env;
100         const path file = env.create_file("file", 42);
101         old_write_time = LastWriteTime(file);
102         file_time_type tp = file_time_type::max();
103         fs::last_write_time(file, tp, ec);
104         new_write_time = LastWriteTime(file);
105     }
106     return !ec && new_write_time > max_sec - 1;
107 }
108 
109 static const bool SupportsNegativeTimes = TestSupportsNegativeTimes();
110 static const bool SupportsMaxTime = TestSupportsMaxTime();
111 
112 } // end namespace
113 
114 // Check if a time point is representable on a given filesystem. Check that:
115 // (A) 'tp' is representable as a time_t
116 // (B) 'tp' is non-negative or the filesystem supports negative times.
117 // (C) 'tp' is not 'file_time_type::max()' or the filesystem supports the max
118 //     value.
TimeIsRepresentableByFilesystem(file_time_type tp)119 inline bool TimeIsRepresentableByFilesystem(file_time_type tp) {
120     using namespace std::chrono;
121     using Lim = std::numeric_limits<std::time_t>;
122     auto sec = duration_cast<seconds>(tp.time_since_epoch()).count();
123     auto microsec = duration_cast<microseconds>(tp.time_since_epoch()).count();
124     if (sec < Lim::min() || sec > Lim::max())   return false;
125     else if (microsec < 0 && !SupportsNegativeTimes) return false;
126     else if (tp == file_time_type::max() && !SupportsMaxTime) return false;
127     return true;
128 }
129 
130 TEST_SUITE(exists_test_suite)
131 
TEST_CASE(signature_test)132 TEST_CASE(signature_test)
133 {
134     const file_time_type t;
135     const path p; ((void)p);
136     std::error_code ec; ((void)ec);
137     ASSERT_SAME_TYPE(decltype(last_write_time(p)), file_time_type);
138     ASSERT_SAME_TYPE(decltype(last_write_time(p, ec)), file_time_type);
139     ASSERT_SAME_TYPE(decltype(last_write_time(p, t)), void);
140     ASSERT_SAME_TYPE(decltype(last_write_time(p, t, ec)), void);
141     ASSERT_NOT_NOEXCEPT(last_write_time(p));
142     ASSERT_NOT_NOEXCEPT(last_write_time(p, t));
143     ASSERT_NOEXCEPT(last_write_time(p, ec));
144     ASSERT_NOEXCEPT(last_write_time(p, t, ec));
145 }
146 
TEST_CASE(read_last_write_time_static_env_test)147 TEST_CASE(read_last_write_time_static_env_test)
148 {
149     using C = file_time_type::clock;
150     file_time_type min = file_time_type::min();
151     {
152         file_time_type ret = last_write_time(StaticEnv::File);
153         TEST_CHECK(ret != min);
154         TEST_CHECK(ret < C::now());
155         TEST_CHECK(C::to_time_t(ret) == LastWriteTime(StaticEnv::File));
156 
157         file_time_type ret2 = last_write_time(StaticEnv::SymlinkToFile);
158         TEST_CHECK(ret == ret2);
159         TEST_CHECK(C::to_time_t(ret2) == LastWriteTime(StaticEnv::SymlinkToFile));
160     }
161     {
162         file_time_type ret = last_write_time(StaticEnv::Dir);
163         TEST_CHECK(ret != min);
164         TEST_CHECK(ret < C::now());
165         TEST_CHECK(C::to_time_t(ret) == LastWriteTime(StaticEnv::Dir));
166 
167         file_time_type ret2 = last_write_time(StaticEnv::SymlinkToDir);
168         TEST_CHECK(ret == ret2);
169         TEST_CHECK(C::to_time_t(ret2) == LastWriteTime(StaticEnv::SymlinkToDir));
170     }
171 }
172 
TEST_CASE(get_last_write_time_dynamic_env_test)173 TEST_CASE(get_last_write_time_dynamic_env_test)
174 {
175     using Clock = file_time_type::clock;
176     using Sec = std::chrono::seconds;
177     scoped_test_env env;
178 
179     const path file = env.create_file("file", 42);
180     const path dir = env.create_dir("dir");
181 
182     const auto file_times = GetTimes(file);
183     const std::time_t file_access_time = file_times.first;
184     const std::time_t file_write_time = file_times.second;
185     const auto dir_times = GetTimes(dir);
186     const std::time_t dir_access_time = dir_times.first;
187     const std::time_t dir_write_time = dir_times.second;
188 
189     file_time_type ftime = last_write_time(file);
190     TEST_CHECK(Clock::to_time_t(ftime) == file_write_time);
191 
192     file_time_type dtime = last_write_time(dir);
193     TEST_CHECK(Clock::to_time_t(dtime) == dir_write_time);
194 
195     SleepFor(Sec(2));
196 
197     // update file and add a file to the directory. Make sure the times increase.
198     std::ofstream of(file, std::ofstream::app);
199     of << "hello";
200     of.close();
201     env.create_file("dir/file1", 1);
202 
203     file_time_type ftime2 = last_write_time(file);
204     file_time_type dtime2 = last_write_time(dir);
205 
206     TEST_CHECK(ftime2 > ftime);
207     TEST_CHECK(dtime2 > dtime);
208     TEST_CHECK(LastAccessTime(file) == file_access_time ||
209                LastAccessTime(file) == Clock::to_time_t(ftime2));
210     TEST_CHECK(LastAccessTime(dir) == dir_access_time);
211 }
212 
213 
TEST_CASE(set_last_write_time_dynamic_env_test)214 TEST_CASE(set_last_write_time_dynamic_env_test)
215 {
216     using Clock = file_time_type::clock;
217     using Sec = std::chrono::seconds;
218     using Hours = std::chrono::hours;
219     using Minutes = std::chrono::minutes;
220     using MicroSec = std::chrono::microseconds;
221     scoped_test_env env;
222 
223     const path file = env.create_file("file", 42);
224     const path dir = env.create_dir("dir");
225     const auto now = Clock::now();
226     const file_time_type epoch_time = now - now.time_since_epoch();
227 
228     const file_time_type future_time = now + Hours(3) + Sec(42) + MicroSec(17);
229     const file_time_type past_time = now - Minutes(3) - Sec(42) - MicroSec(17);
230     const file_time_type before_epoch_time = epoch_time - Minutes(3) - Sec(42) - MicroSec(17);
231     // FreeBSD has a bug in their utimes implementation where the time is not update
232     // when the number of seconds is '-1'.
233 #if defined(__FreeBSD__)
234     const file_time_type just_before_epoch_time = epoch_time - Sec(2) - MicroSec(17);
235 #else
236     const file_time_type just_before_epoch_time = epoch_time - MicroSec(17);
237 #endif
238 
239     struct TestCase {
240       path p;
241       file_time_type new_time;
242     } cases[] = {
243         {file, epoch_time},
244         {dir, epoch_time},
245         {file, future_time},
246         {dir, future_time},
247         {file, past_time},
248         {dir, past_time},
249         {file, before_epoch_time},
250         {dir, before_epoch_time},
251         {file, just_before_epoch_time},
252         {dir, just_before_epoch_time}
253     };
254     for (const auto& TC : cases) {
255         const auto old_times = GetTimes(TC.p);
256         file_time_type old_time(Sec(old_times.second));
257 
258         std::error_code ec = GetTestEC();
259         last_write_time(TC.p, TC.new_time, ec);
260         TEST_CHECK(!ec);
261 
262         file_time_type  got_time = last_write_time(TC.p);
263 
264         if (TimeIsRepresentableByFilesystem(TC.new_time)) {
265             TEST_CHECK(got_time != old_time);
266             if (TC.new_time < epoch_time) {
267                 TEST_CHECK(got_time <= TC.new_time);
268                 TEST_CHECK(got_time > TC.new_time - Sec(1));
269             } else {
270                 TEST_CHECK(got_time <= TC.new_time + Sec(1));
271                 TEST_CHECK(got_time >= TC.new_time - Sec(1));
272             }
273             TEST_CHECK(LastAccessTime(TC.p) == old_times.first);
274         }
275     }
276 }
277 
TEST_CASE(last_write_time_symlink_test)278 TEST_CASE(last_write_time_symlink_test)
279 {
280     using Clock = file_time_type::clock;
281     using Sec = std::chrono::seconds;
282     using Hours = std::chrono::hours;
283     using Minutes = std::chrono::minutes;
284 
285     scoped_test_env env;
286 
287     const path file = env.create_file("file", 42);
288     const path sym = env.create_symlink("file", "sym");
289 
290     const file_time_type new_time = Clock::now() + Hours(3);
291 
292     const auto old_times = GetTimes(sym);
293     const auto old_sym_times = GetSymlinkTimes(sym);
294 
295     std::error_code ec = GetTestEC();
296     last_write_time(sym, new_time, ec);
297     TEST_CHECK(!ec);
298 
299     const std::time_t new_time_t = Clock::to_time_t(new_time);
300     file_time_type  got_time = last_write_time(sym);
301     std::time_t got_time_t = Clock::to_time_t(got_time);
302 
303     TEST_CHECK(got_time_t != old_times.second);
304     TEST_CHECK(got_time_t == new_time_t);
305     TEST_CHECK(LastWriteTime(file) == new_time_t);
306     TEST_CHECK(LastAccessTime(sym) == old_times.first);
307     TEST_CHECK(GetSymlinkTimes(sym) == old_sym_times);
308 }
309 
310 
TEST_CASE(test_write_min_time)311 TEST_CASE(test_write_min_time)
312 {
313     using Clock = file_time_type::clock;
314     using Sec = std::chrono::seconds;
315     using MicroSec = std::chrono::microseconds;
316     using Lim = std::numeric_limits<std::time_t>;
317     scoped_test_env env;
318     const path p = env.create_file("file", 42);
319 
320     std::error_code ec = GetTestEC();
321     file_time_type new_time = file_time_type::min();
322 
323     last_write_time(p, new_time, ec);
324     file_time_type tt = last_write_time(p);
325 
326     if (TimeIsRepresentableByFilesystem(new_time)) {
327         TEST_CHECK(!ec);
328         TEST_CHECK(tt >= new_time);
329         TEST_CHECK(tt < new_time + Sec(1));
330     }
331 
332     ec = GetTestEC();
333     last_write_time(p, Clock::now());
334 
335     new_time = file_time_type::min() + MicroSec(1);
336 
337     last_write_time(p, new_time, ec);
338     tt = last_write_time(p);
339 
340     if (TimeIsRepresentableByFilesystem(new_time)) {
341         TEST_CHECK(!ec);
342         TEST_CHECK(tt >= new_time);
343         TEST_CHECK(tt < new_time + Sec(1));
344     }
345 }
346 
347 
348 
TEST_CASE(test_write_min_max_time)349 TEST_CASE(test_write_min_max_time)
350 {
351     using Clock = file_time_type::clock;
352     using Sec = std::chrono::seconds;
353     using Hours = std::chrono::hours;
354     using Lim = std::numeric_limits<std::time_t>;
355     scoped_test_env env;
356     const path p = env.create_file("file", 42);
357 
358     std::error_code ec = GetTestEC();
359     file_time_type new_time = file_time_type::max();
360 
361     ec = GetTestEC();
362     last_write_time(p, new_time, ec);
363     file_time_type tt = last_write_time(p);
364 
365     if (TimeIsRepresentableByFilesystem(new_time)) {
366         TEST_CHECK(!ec);
367         TEST_CHECK(tt > new_time - Sec(1));
368         TEST_CHECK(tt <= new_time);
369     }
370 }
371 
TEST_CASE(test_value_on_failure)372 TEST_CASE(test_value_on_failure)
373 {
374     const path p = StaticEnv::DNE;
375     std::error_code ec = GetTestEC();
376     TEST_CHECK(last_write_time(p, ec) == file_time_type::min());
377     TEST_CHECK(ec);
378     TEST_CHECK(ec != GetTestEC());
379 }
380 
TEST_CASE(test_exists_fails)381 TEST_CASE(test_exists_fails)
382 {
383     scoped_test_env env;
384     const path dir = env.create_dir("dir");
385     const path file = env.create_file("dir/file", 42);
386     permissions(dir, perms::none);
387 
388     std::error_code ec = GetTestEC();
389     TEST_CHECK(last_write_time(file, ec) == file_time_type::min());
390     TEST_CHECK(ec);
391     TEST_CHECK(ec != GetTestEC());
392 
393     TEST_CHECK_THROW(filesystem_error, last_write_time(file));
394 }
395 
396 TEST_SUITE_END()
397