stat-time-tests: minor cleanups
[pspp] / tests / test-stat-time.c
1 /* Test of <stat-time.h>.
2    Copyright (C) 2007-2009 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
16
17 /* Written by James Youngman <jay@gnu.org>, 2007.  */
18
19 #include <config.h>
20
21 #include "stat-time.h"
22
23 #include <fcntl.h>
24 #include <signal.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <sys/stat.h>
28 #include <unistd.h>
29
30 #define ASSERT(expr) \
31   do                                                                         \
32     {                                                                        \
33       if (!(expr))                                                           \
34         {                                                                    \
35           fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__); \
36           fflush (stderr);                                                   \
37           abort ();                                                          \
38         }                                                                    \
39     }                                                                        \
40   while (0)
41
42 enum { NFILES = 4 };
43
44 static void
45 force_unlink (const char *filename)
46 {
47   /* This chmod is necessary on mingw, where unlink() of a read-only file
48      fails with EPERM.  */
49   chmod (filename, 0600);
50   unlink (filename);
51 }
52
53 static void
54 cleanup (int sig)
55 {
56   /* Remove temporary files.  */
57   force_unlink ("t-stt-stamp1");
58   force_unlink ("t-stt-testfile");
59   force_unlink ("t-stt-stamp2");
60   force_unlink ("t-stt-renamed");
61   force_unlink ("t-stt-stamp3");
62
63   if (sig != 0)
64     _exit (1);
65 }
66
67 static int
68 open_file (const char *filename, int flags)
69 {
70   int fd = open (filename, flags | O_WRONLY, 0500);
71   if (fd >= 0)
72     {
73       close (fd);
74       return 1;
75     }
76   else
77     {
78       return 0;
79     }
80 }
81
82 static void
83 create_file (const char *filename)
84 {
85   ASSERT (open_file (filename, O_CREAT | O_EXCL));
86 }
87
88 static void
89 do_stat (const char *filename, struct stat *p)
90 {
91   ASSERT (stat (filename, p) == 0);
92 }
93
94 /* Sleep long enough to notice a timestamp difference on the file
95    system in the current directory.  */
96 static void
97 nap (void)
98 {
99 #if !HAVE_USLEEP
100   /* Assume the worst case file system of FAT, which has a granularity
101      of 2 seconds.  */
102   sleep (2);
103 #else /* HAVE_USLEEP */
104   static long delay;
105   if (!delay)
106     {
107       /* Initialize only once, by sleeping for 15 milliseconds (needed
108          since xfs has a quantization of about 10 milliseconds, even
109          though it has a granularity of 1 nanosecond).  If the seconds
110          differ, repeat the test one more time (in case we crossed a
111          quantization boundary on a file system with 1 second
112          resolution).  If we can't observe a difference in only the
113          nanoseconds, then fall back to 2 seconds.  */
114       struct stat st1;
115       struct stat st2;
116       ASSERT (stat ("t-stt-stamp1", &st1) == 0);
117       ASSERT (unlink ("t-stt-stamp1") == 0);
118       delay = 15000;
119       usleep (delay);
120       create_file ("t-stt-stamp1");
121       ASSERT (stat ("t-stt-stamp1", &st2) == 0);
122       if (st1.st_mtime != st2.st_mtime)
123         {
124           /* Seconds differ, give it one more shot.  */
125           st1 = st2;
126           ASSERT (unlink ("t-stt-stamp1") == 0);
127           usleep (delay);
128           create_file ("t-stt-stamp1");
129           ASSERT (stat ("t-stt-stamp1", &st2) == 0);
130         }
131       if (! (st1.st_mtime == st2.st_mtime
132              && get_stat_mtime_ns (&st1) < get_stat_mtime_ns (&st2)))
133         delay = 2000000;
134     }
135   usleep (delay);
136 #endif /* HAVE_USLEEP */
137 }
138
139 static void
140 prepare_test (struct stat *statinfo, struct timespec *modtimes)
141 {
142   int i;
143
144   create_file ("t-stt-stamp1");
145   nap ();
146   create_file ("t-stt-testfile");
147   nap ();
148   create_file ("t-stt-stamp2");
149   nap ();
150   ASSERT (chmod ("t-stt-testfile", 0400) == 0);
151   nap ();
152   create_file ("t-stt-stamp3");
153
154   do_stat ("t-stt-stamp1",  &statinfo[0]);
155   do_stat ("t-stt-testfile", &statinfo[1]);
156   do_stat ("t-stt-stamp2",  &statinfo[2]);
157   do_stat ("t-stt-stamp3",  &statinfo[3]);
158
159   /* Now use our access functions. */
160   for (i = 0; i < NFILES; ++i)
161     {
162       modtimes[i] = get_stat_mtime (&statinfo[i]);
163     }
164 }
165
166 static void
167 test_mtime (const struct stat *statinfo, struct timespec *modtimes)
168 {
169   int i;
170
171   /* Use the struct stat fields directly. */
172   /* mtime(stamp1) < mtime(stamp2) */
173   ASSERT (statinfo[0].st_mtime < statinfo[2].st_mtime
174           || (statinfo[0].st_mtime == statinfo[2].st_mtime
175               && (get_stat_mtime_ns (&statinfo[0])
176                   < get_stat_mtime_ns (&statinfo[2]))));
177   /* mtime(stamp2) < mtime(stamp3) */
178   ASSERT (statinfo[2].st_mtime < statinfo[3].st_mtime
179           || (statinfo[2].st_mtime == statinfo[3].st_mtime
180               && (get_stat_mtime_ns (&statinfo[2])
181                   < get_stat_mtime_ns (&statinfo[3]))));
182
183   /* Now check the result of the access functions. */
184   /* mtime(stamp1) < mtime(stamp2) */
185   ASSERT (modtimes[0].tv_sec < modtimes[2].tv_sec
186           || (modtimes[0].tv_sec == modtimes[2].tv_sec
187               && modtimes[0].tv_nsec < modtimes[2].tv_nsec));
188   /* mtime(stamp2) < mtime(stamp3) */
189   ASSERT (modtimes[2].tv_sec < modtimes[3].tv_sec
190           || (modtimes[2].tv_sec == modtimes[3].tv_sec
191               && modtimes[2].tv_nsec < modtimes[3].tv_nsec));
192
193   /* verify equivalence */
194   for (i = 0; i < NFILES; ++i)
195     {
196       struct timespec ts;
197       ts = get_stat_mtime (&statinfo[i]);
198       ASSERT (ts.tv_sec == statinfo[i].st_mtime);
199     }
200 }
201
202 #if (defined _WIN32 || defined __WIN32__) && !defined __CYGWIN__
203 /* Skip the ctime tests on native Windows platforms, because their
204    st_ctime is either the same as st_mtime (plus or minus an offset)
205    or set to the file _creation_ time, and is not influenced by rename
206    or chmod.  */
207 # define test_ctime ((void) 0)
208 #else
209 static void
210 test_ctime (const struct stat *statinfo)
211 {
212   /* On some buggy NFS clients, mtime and ctime are disproportionately
213      skewed from one another.  Skip this test in that case.  */
214   if (statinfo[0].st_mtime != statinfo[0].st_ctime)
215     return;
216
217   /* mtime(stamp2) < ctime(renamed) */
218   ASSERT (statinfo[2].st_mtime < statinfo[1].st_ctime
219           || (statinfo[2].st_mtime == statinfo[1].st_ctime
220               && (get_stat_mtime_ns (&statinfo[2])
221                   < get_stat_ctime_ns (&statinfo[1]))));
222 }
223 #endif
224
225 static void
226 test_birthtime (const struct stat *statinfo,
227                 const struct timespec *modtimes,
228                 struct timespec *birthtimes)
229 {
230   int i;
231
232   /* Collect the birth times.. */
233   for (i = 0; i < NFILES; ++i)
234     {
235       birthtimes[i] = get_stat_birthtime (&statinfo[i]);
236       if (birthtimes[i].tv_nsec < 0)
237         return;
238     }
239
240   /* mtime(stamp1) < birthtime(renamed) */
241   ASSERT (modtimes[0].tv_sec < birthtimes[1].tv_sec
242           || (modtimes[0].tv_sec == birthtimes[1].tv_sec
243               && modtimes[0].tv_nsec < birthtimes[1].tv_nsec));
244   /* birthtime(renamed) < mtime(stamp2) */
245   ASSERT (birthtimes[1].tv_sec < modtimes[2].tv_sec
246           || (birthtimes[1].tv_sec == modtimes[2].tv_sec
247               && birthtimes[1].tv_nsec < modtimes[2].tv_nsec));
248 }
249
250 int
251 main ()
252 {
253   struct stat statinfo[NFILES];
254   struct timespec modtimes[NFILES];
255   struct timespec birthtimes[NFILES];
256
257 #ifdef SIGHUP
258   signal (SIGHUP, cleanup);
259 #endif
260 #ifdef SIGINT
261   signal (SIGINT, cleanup);
262 #endif
263 #ifdef SIGQUIT
264   signal (SIGQUIT, cleanup);
265 #endif
266 #ifdef SIGTERM
267   signal (SIGTERM, cleanup);
268 #endif
269
270   cleanup (0);
271   prepare_test (statinfo, modtimes);
272   test_mtime (statinfo, modtimes);
273   test_ctime (statinfo);
274   test_birthtime (statinfo, modtimes, birthtimes);
275
276   cleanup (0);
277   return 0;
278 }