• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Tests of the C++ interface to POSIX functions that require mocks
2 //
3 // Copyright (c) 2012 - present, Victor Zverovich
4 // All rights reserved.
5 //
6 // For the license information refer to format.h.
7 
8 // Disable bogus MSVC warnings.
9 #if !defined(_CRT_SECURE_NO_WARNINGS) && defined(_MSC_VER)
10 #  define _CRT_SECURE_NO_WARNINGS
11 #endif
12 
13 #include "posix-mock.h"
14 
15 #include <errno.h>
16 #include <fcntl.h>
17 
18 #include <climits>
19 #include <memory>
20 
21 #include "../src/os.cc"
22 
23 #ifdef _WIN32
24 #  include <io.h>
25 #  undef max
26 #endif
27 
28 #include "gmock/gmock.h"
29 #include "gtest-extra.h"
30 #include "util.h"
31 
32 using fmt::buffered_file;
33 
34 using testing::_;
35 using testing::Return;
36 using testing::StrEq;
37 
38 template <typename Mock> struct scoped_mock : testing::StrictMock<Mock> {
scoped_mockscoped_mock39   scoped_mock() { Mock::instance = this; }
~scoped_mockscoped_mock40   ~scoped_mock() { Mock::instance = nullptr; }
41 };
42 
43 namespace {
44 int open_count;
45 int close_count;
46 int dup_count;
47 int dup2_count;
48 int fdopen_count;
49 int read_count;
50 int write_count;
51 int pipe_count;
52 int fopen_count;
53 int fclose_count;
54 int fileno_count;
55 size_t read_nbyte;
56 size_t write_nbyte;
57 bool sysconf_error;
58 
59 enum { none, max_size, error } fstat_sim;
60 }  // namespace
61 
62 #define EMULATE_EINTR(func, error_result) \
63   if (func##_count != 0) {                \
64     if (func##_count++ != 3) {            \
65       errno = EINTR;                      \
66       return error_result;                \
67     }                                     \
68   }
69 
70 #ifndef _MSC_VER
open(const char * path,int oflag,int mode)71 int test::open(const char* path, int oflag, int mode) {
72   EMULATE_EINTR(open, -1);
73   return ::open(path, oflag, mode);
74 }
75 #endif
76 
77 #ifndef _WIN32
78 
sysconf(int name)79 long test::sysconf(int name) {
80   long result = ::sysconf(name);
81   if (!sysconf_error) return result;
82   // Simulate an error.
83   errno = EINVAL;
84   return -1;
85 }
86 
max_file_size()87 static off_t max_file_size() { return std::numeric_limits<off_t>::max(); }
88 
fstat(int fd,struct stat * buf)89 int test::fstat(int fd, struct stat* buf) {
90   int result = ::fstat(fd, buf);
91   if (fstat_sim == max_size) buf->st_size = max_file_size();
92   return result;
93 }
94 
95 #else
96 
max_file_size()97 static LONGLONG max_file_size() { return std::numeric_limits<LONGLONG>::max(); }
98 
GetFileSize(HANDLE hFile,LPDWORD lpFileSizeHigh)99 DWORD test::GetFileSize(HANDLE hFile, LPDWORD lpFileSizeHigh) {
100   if (fstat_sim == error) {
101     SetLastError(ERROR_ACCESS_DENIED);
102     return INVALID_FILE_SIZE;
103   }
104   if (fstat_sim == max_size) {
105     DWORD max = std::numeric_limits<DWORD>::max();
106     *lpFileSizeHigh = max >> 1;
107     return max;
108   }
109   return ::GetFileSize(hFile, lpFileSizeHigh);
110 }
111 
112 #endif
113 
close(int fildes)114 int test::close(int fildes) {
115   // Close the file first because close shouldn't be retried.
116   int result = ::FMT_POSIX(close(fildes));
117   EMULATE_EINTR(close, -1);
118   return result;
119 }
120 
dup(int fildes)121 int test::dup(int fildes) {
122   EMULATE_EINTR(dup, -1);
123   return ::FMT_POSIX(dup(fildes));
124 }
125 
dup2(int fildes,int fildes2)126 int test::dup2(int fildes, int fildes2) {
127   EMULATE_EINTR(dup2, -1);
128   return ::FMT_POSIX(dup2(fildes, fildes2));
129 }
130 
fdopen(int fildes,const char * mode)131 FILE* test::fdopen(int fildes, const char* mode) {
132   EMULATE_EINTR(fdopen, nullptr);
133   return ::FMT_POSIX(fdopen(fildes, mode));
134 }
135 
read(int fildes,void * buf,test::size_t nbyte)136 test::ssize_t test::read(int fildes, void* buf, test::size_t nbyte) {
137   read_nbyte = nbyte;
138   EMULATE_EINTR(read, -1);
139   return ::FMT_POSIX(read(fildes, buf, nbyte));
140 }
141 
write(int fildes,const void * buf,test::size_t nbyte)142 test::ssize_t test::write(int fildes, const void* buf, test::size_t nbyte) {
143   write_nbyte = nbyte;
144   EMULATE_EINTR(write, -1);
145   return ::FMT_POSIX(write(fildes, buf, nbyte));
146 }
147 
148 #ifndef _WIN32
pipe(int fildes[2])149 int test::pipe(int fildes[2]) {
150   EMULATE_EINTR(pipe, -1);
151   return ::pipe(fildes);
152 }
153 #else
pipe(int * pfds,unsigned psize,int textmode)154 int test::pipe(int* pfds, unsigned psize, int textmode) {
155   EMULATE_EINTR(pipe, -1);
156   return _pipe(pfds, psize, textmode);
157 }
158 #endif
159 
fopen(const char * filename,const char * mode)160 FILE* test::fopen(const char* filename, const char* mode) {
161   EMULATE_EINTR(fopen, nullptr);
162   return ::fopen(filename, mode);
163 }
164 
fclose(FILE * stream)165 int test::fclose(FILE* stream) {
166   EMULATE_EINTR(fclose, EOF);
167   return ::fclose(stream);
168 }
169 
170 int(test::fileno)(FILE* stream) {
171   EMULATE_EINTR(fileno, -1);
172 #ifdef fileno
173   return FMT_POSIX(fileno(stream));
174 #else
175   return ::FMT_POSIX(fileno(stream));
176 #endif
177 }
178 
179 #ifndef _WIN32
180 #  define EXPECT_RETRY(statement, func, message) \
181     func##_count = 1;                            \
182     statement;                                   \
183     EXPECT_EQ(4, func##_count);                  \
184     func##_count = 0;
185 #  define EXPECT_EQ_POSIX(expected, actual) EXPECT_EQ(expected, actual)
186 #else
187 #  define EXPECT_RETRY(statement, func, message)    \
188     func##_count = 1;                               \
189     EXPECT_SYSTEM_ERROR(statement, EINTR, message); \
190     func##_count = 0;
191 #  define EXPECT_EQ_POSIX(expected, actual)
192 #endif
193 
194 #if FMT_USE_FCNTL
write_file(fmt::cstring_view filename,fmt::string_view content)195 void write_file(fmt::cstring_view filename, fmt::string_view content) {
196   fmt::buffered_file f(filename, "w");
197   f.print("{}", content);
198 }
199 
200 using fmt::file;
201 
TEST(os_test,getpagesize)202 TEST(os_test, getpagesize) {
203 #  ifdef _WIN32
204   SYSTEM_INFO si = {};
205   GetSystemInfo(&si);
206   EXPECT_EQ(si.dwPageSize, fmt::getpagesize());
207 #  else
208   EXPECT_EQ(sysconf(_SC_PAGESIZE), fmt::getpagesize());
209   sysconf_error = true;
210   EXPECT_SYSTEM_ERROR(fmt::getpagesize(), EINVAL,
211                       "cannot get memory page size");
212   sysconf_error = false;
213 #  endif
214 }
215 
TEST(file_test,open_retry)216 TEST(file_test, open_retry) {
217 #  ifndef _WIN32
218   write_file("temp", "there must be something here");
219   std::unique_ptr<file> f{nullptr};
220   EXPECT_RETRY(f.reset(new file("temp", file::RDONLY)), open,
221                "cannot open file temp");
222   char c = 0;
223   f->read(&c, 1);
224 #  endif
225 }
226 
TEST(file_test,close_no_retry_in_dtor)227 TEST(file_test, close_no_retry_in_dtor) {
228   file read_end, write_end;
229   file::pipe(read_end, write_end);
230   std::unique_ptr<file> f(new file(std::move(read_end)));
231   int saved_close_count = 0;
232   EXPECT_WRITE(
233       stderr,
234       {
235         close_count = 1;
236         f.reset(nullptr);
237         saved_close_count = close_count;
238         close_count = 0;
239       },
240       system_error_message(EINTR, "cannot close file") + "\n");
241   EXPECT_EQ(2, saved_close_count);
242 }
243 
TEST(file_test,close_no_retry)244 TEST(file_test, close_no_retry) {
245   file read_end, write_end;
246   file::pipe(read_end, write_end);
247   close_count = 1;
248   EXPECT_SYSTEM_ERROR(read_end.close(), EINTR, "cannot close file");
249   EXPECT_EQ(2, close_count);
250   close_count = 0;
251 }
252 
TEST(file_test,size)253 TEST(file_test, size) {
254   std::string content = "top secret, destroy before reading";
255   write_file("temp", content);
256   file f("temp", file::RDONLY);
257   EXPECT_GE(f.size(), 0);
258   EXPECT_EQ(content.size(), static_cast<unsigned long long>(f.size()));
259 #  ifdef _WIN32
260   auto error_code = std::error_code();
261   fstat_sim = error;
262   try {
263     f.size();
264   } catch (const std::system_error& e) {
265     error_code = e.code();
266   }
267   fstat_sim = none;
268   EXPECT_EQ(error_code,
269             std::error_code(ERROR_ACCESS_DENIED, fmt::system_category()));
270 #  else
271   f.close();
272   EXPECT_SYSTEM_ERROR(f.size(), EBADF, "cannot get file attributes");
273 #  endif
274 }
275 
TEST(file_test,max_size)276 TEST(file_test, max_size) {
277   write_file("temp", "");
278   file f("temp", file::RDONLY);
279   fstat_sim = max_size;
280   EXPECT_GE(f.size(), 0);
281   EXPECT_EQ(max_file_size(), f.size());
282   fstat_sim = none;
283 }
284 
TEST(file_test,read_retry)285 TEST(file_test, read_retry) {
286   file read_end, write_end;
287   file::pipe(read_end, write_end);
288   enum { SIZE = 4 };
289   write_end.write("test", SIZE);
290   write_end.close();
291   char buffer[SIZE];
292   size_t count = 0;
293   EXPECT_RETRY(count = read_end.read(buffer, SIZE), read,
294                "cannot read from file");
295   EXPECT_EQ_POSIX(static_cast<std::streamsize>(SIZE), count);
296 }
297 
TEST(file_test,write_retry)298 TEST(file_test, write_retry) {
299   file read_end, write_end;
300   file::pipe(read_end, write_end);
301   enum { SIZE = 4 };
302   size_t count = 0;
303   EXPECT_RETRY(count = write_end.write("test", SIZE), write,
304                "cannot write to file");
305   write_end.close();
306 #  ifndef _WIN32
307   EXPECT_EQ(static_cast<std::streamsize>(SIZE), count);
308   char buffer[SIZE + 1];
309   read_end.read(buffer, SIZE);
310   buffer[SIZE] = '\0';
311   EXPECT_STREQ("test", buffer);
312 #  endif
313 }
314 
315 #  ifdef _WIN32
TEST(file_test,convert_read_count)316 TEST(file_test, convert_read_count) {
317   file read_end, write_end;
318   file::pipe(read_end, write_end);
319   char c;
320   size_t size = UINT_MAX;
321   if (sizeof(unsigned) != sizeof(size_t)) ++size;
322   read_count = 1;
323   read_nbyte = 0;
324   EXPECT_THROW(read_end.read(&c, size), std::system_error);
325   read_count = 0;
326   EXPECT_EQ(UINT_MAX, read_nbyte);
327 }
328 
TEST(file_test,convert_write_count)329 TEST(file_test, convert_write_count) {
330   file read_end, write_end;
331   file::pipe(read_end, write_end);
332   char c;
333   size_t size = UINT_MAX;
334   if (sizeof(unsigned) != sizeof(size_t)) ++size;
335   write_count = 1;
336   write_nbyte = 0;
337   EXPECT_THROW(write_end.write(&c, size), std::system_error);
338   write_count = 0;
339   EXPECT_EQ(UINT_MAX, write_nbyte);
340 }
341 #  endif
342 
TEST(file_test,dup_no_retry)343 TEST(file_test, dup_no_retry) {
344   int stdout_fd = FMT_POSIX(fileno(stdout));
345   dup_count = 1;
346   EXPECT_SYSTEM_ERROR(
347       file::dup(stdout_fd), EINTR,
348       fmt::format("cannot duplicate file descriptor {}", stdout_fd));
349   dup_count = 0;
350 }
351 
TEST(file_test,dup2_retry)352 TEST(file_test, dup2_retry) {
353   int stdout_fd = FMT_POSIX(fileno(stdout));
354   file f1 = file::dup(stdout_fd), f2 = file::dup(stdout_fd);
355   EXPECT_RETRY(f1.dup2(f2.descriptor()), dup2,
356                fmt::format("cannot duplicate file descriptor {} to {}",
357                            f1.descriptor(), f2.descriptor()));
358 }
359 
TEST(file_test,dup2_no_except_retry)360 TEST(file_test, dup2_no_except_retry) {
361   int stdout_fd = FMT_POSIX(fileno(stdout));
362   file f1 = file::dup(stdout_fd), f2 = file::dup(stdout_fd);
363   std::error_code ec;
364   dup2_count = 1;
365   f1.dup2(f2.descriptor(), ec);
366 #  ifndef _WIN32
367   EXPECT_EQ(4, dup2_count);
368 #  else
369   EXPECT_EQ(EINTR, ec.value());
370 #  endif
371   dup2_count = 0;
372 }
373 
TEST(file_test,pipe_no_retry)374 TEST(file_test, pipe_no_retry) {
375   file read_end, write_end;
376   pipe_count = 1;
377   EXPECT_SYSTEM_ERROR(file::pipe(read_end, write_end), EINTR,
378                       "cannot create pipe");
379   pipe_count = 0;
380 }
381 
TEST(file_test,fdopen_no_retry)382 TEST(file_test, fdopen_no_retry) {
383   file read_end, write_end;
384   file::pipe(read_end, write_end);
385   fdopen_count = 1;
386   EXPECT_SYSTEM_ERROR(read_end.fdopen("r"), EINTR,
387                       "cannot associate stream with file descriptor");
388   fdopen_count = 0;
389 }
390 
TEST(buffered_file_test,open_retry)391 TEST(buffered_file_test, open_retry) {
392   write_file("temp", "there must be something here");
393   std::unique_ptr<buffered_file> f{nullptr};
394   EXPECT_RETRY(f.reset(new buffered_file("temp", "r")), fopen,
395                "cannot open file temp");
396 #  ifndef _WIN32
397   char c = 0;
398   if (fread(&c, 1, 1, f->get()) < 1)
399     throw fmt::system_error(errno, "fread failed");
400 #  endif
401 }
402 
TEST(buffered_file_test,close_no_retry_in_dtor)403 TEST(buffered_file_test, close_no_retry_in_dtor) {
404   file read_end, write_end;
405   file::pipe(read_end, write_end);
406   std::unique_ptr<buffered_file> f(new buffered_file(read_end.fdopen("r")));
407   int saved_fclose_count = 0;
408   EXPECT_WRITE(
409       stderr,
410       {
411         fclose_count = 1;
412         f.reset(nullptr);
413         saved_fclose_count = fclose_count;
414         fclose_count = 0;
415       },
416       system_error_message(EINTR, "cannot close file") + "\n");
417   EXPECT_EQ(2, saved_fclose_count);
418 }
419 
TEST(buffered_file_test,close_no_retry)420 TEST(buffered_file_test, close_no_retry) {
421   file read_end, write_end;
422   file::pipe(read_end, write_end);
423   buffered_file f = read_end.fdopen("r");
424   fclose_count = 1;
425   EXPECT_SYSTEM_ERROR(f.close(), EINTR, "cannot close file");
426   EXPECT_EQ(2, fclose_count);
427   fclose_count = 0;
428 }
429 
TEST(buffered_file_test,fileno_no_retry)430 TEST(buffered_file_test, fileno_no_retry) {
431   file read_end, write_end;
432   file::pipe(read_end, write_end);
433   buffered_file f = read_end.fdopen("r");
434   fileno_count = 1;
435   EXPECT_SYSTEM_ERROR((f.descriptor)(), EINTR, "cannot get file descriptor");
436   EXPECT_EQ(2, fileno_count);
437   fileno_count = 0;
438 }
439 #endif  // FMT_USE_FCNTL
440 
441 struct test_mock {
442   static test_mock* instance;
443 } * test_mock::instance;
444 
TEST(scoped_mock,scope)445 TEST(scoped_mock, scope) {
446   {
447     scoped_mock<test_mock> mock;
448     EXPECT_EQ(&mock, test_mock::instance);
449     test_mock& copy = mock;
450     static_cast<void>(copy);
451   }
452   EXPECT_EQ(nullptr, test_mock::instance);
453 }
454