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 #ifndef _CRT_SECURE_NO_WARNINGS
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 # undef ERROR
27 #endif
28
29 #include "gmock.h"
30 #include "gtest-extra.h"
31 #include "util.h"
32
33 using fmt::buffered_file;
34 using fmt::error_code;
35
36 using testing::_;
37 using testing::Return;
38 using testing::StrEq;
39
40 namespace {
41 int open_count;
42 int close_count;
43 int dup_count;
44 int dup2_count;
45 int fdopen_count;
46 int read_count;
47 int write_count;
48 int pipe_count;
49 int fopen_count;
50 int fclose_count;
51 int fileno_count;
52 size_t read_nbyte;
53 size_t write_nbyte;
54 bool sysconf_error;
55
56 enum { NONE, MAX_SIZE, ERROR } fstat_sim;
57 } // namespace
58
59 #define EMULATE_EINTR(func, error_result) \
60 if (func##_count != 0) { \
61 if (func##_count++ != 3) { \
62 errno = EINTR; \
63 return error_result; \
64 } \
65 }
66
67 #ifndef _MSC_VER
open(const char * path,int oflag,int mode)68 int test::open(const char* path, int oflag, int mode) {
69 EMULATE_EINTR(open, -1);
70 return ::open(path, oflag, mode);
71 }
72 #else
sopen_s(int * pfh,const char * filename,int oflag,int shflag,int pmode)73 errno_t test::sopen_s(int* pfh, const char* filename, int oflag, int shflag,
74 int pmode) {
75 EMULATE_EINTR(open, EINTR);
76 return _sopen_s(pfh, filename, oflag, shflag, pmode);
77 }
78 #endif
79
80 #ifndef _WIN32
81
sysconf(int name)82 long test::sysconf(int name) {
83 long result = ::sysconf(name);
84 if (!sysconf_error) return result;
85 // Simulate an error.
86 errno = EINVAL;
87 return -1;
88 }
89
max_file_size()90 static off_t max_file_size() { return std::numeric_limits<off_t>::max(); }
91
fstat(int fd,struct stat * buf)92 int test::fstat(int fd, struct stat* buf) {
93 int result = ::fstat(fd, buf);
94 if (fstat_sim == MAX_SIZE) buf->st_size = max_file_size();
95 return result;
96 }
97
98 #else
99
max_file_size()100 static LONGLONG max_file_size() { return std::numeric_limits<LONGLONG>::max(); }
101
GetFileSize(HANDLE hFile,LPDWORD lpFileSizeHigh)102 DWORD test::GetFileSize(HANDLE hFile, LPDWORD lpFileSizeHigh) {
103 if (fstat_sim == ERROR) {
104 SetLastError(ERROR_ACCESS_DENIED);
105 return INVALID_FILE_SIZE;
106 }
107 if (fstat_sim == MAX_SIZE) {
108 DWORD max = std::numeric_limits<DWORD>::max();
109 *lpFileSizeHigh = max >> 1;
110 return max;
111 }
112 return ::GetFileSize(hFile, lpFileSizeHigh);
113 }
114
115 #endif
116
close(int fildes)117 int test::close(int fildes) {
118 // Close the file first because close shouldn't be retried.
119 int result = ::FMT_POSIX(close(fildes));
120 EMULATE_EINTR(close, -1);
121 return result;
122 }
123
dup(int fildes)124 int test::dup(int fildes) {
125 EMULATE_EINTR(dup, -1);
126 return ::FMT_POSIX(dup(fildes));
127 }
128
dup2(int fildes,int fildes2)129 int test::dup2(int fildes, int fildes2) {
130 EMULATE_EINTR(dup2, -1);
131 return ::FMT_POSIX(dup2(fildes, fildes2));
132 }
133
fdopen(int fildes,const char * mode)134 FILE* test::fdopen(int fildes, const char* mode) {
135 EMULATE_EINTR(fdopen, nullptr);
136 return ::FMT_POSIX(fdopen(fildes, mode));
137 }
138
read(int fildes,void * buf,test::size_t nbyte)139 test::ssize_t test::read(int fildes, void* buf, test::size_t nbyte) {
140 read_nbyte = nbyte;
141 EMULATE_EINTR(read, -1);
142 return ::FMT_POSIX(read(fildes, buf, nbyte));
143 }
144
write(int fildes,const void * buf,test::size_t nbyte)145 test::ssize_t test::write(int fildes, const void* buf, test::size_t nbyte) {
146 write_nbyte = nbyte;
147 EMULATE_EINTR(write, -1);
148 return ::FMT_POSIX(write(fildes, buf, nbyte));
149 }
150
151 #ifndef _WIN32
pipe(int fildes[2])152 int test::pipe(int fildes[2]) {
153 EMULATE_EINTR(pipe, -1);
154 return ::pipe(fildes);
155 }
156 #else
pipe(int * pfds,unsigned psize,int textmode)157 int test::pipe(int* pfds, unsigned psize, int textmode) {
158 EMULATE_EINTR(pipe, -1);
159 return _pipe(pfds, psize, textmode);
160 }
161 #endif
162
fopen(const char * filename,const char * mode)163 FILE* test::fopen(const char* filename, const char* mode) {
164 EMULATE_EINTR(fopen, nullptr);
165 return ::fopen(filename, mode);
166 }
167
fclose(FILE * stream)168 int test::fclose(FILE* stream) {
169 EMULATE_EINTR(fclose, EOF);
170 return ::fclose(stream);
171 }
172
173 int(test::fileno)(FILE* stream) {
174 EMULATE_EINTR(fileno, -1);
175 #ifdef fileno
176 return FMT_POSIX(fileno(stream));
177 #else
178 return ::FMT_POSIX(fileno(stream));
179 #endif
180 }
181
182 #ifndef _WIN32
183 # define EXPECT_RETRY(statement, func, message) \
184 func##_count = 1; \
185 statement; \
186 EXPECT_EQ(4, func##_count); \
187 func##_count = 0;
188 # define EXPECT_EQ_POSIX(expected, actual) EXPECT_EQ(expected, actual)
189 #else
190 # define EXPECT_RETRY(statement, func, message) \
191 func##_count = 1; \
192 EXPECT_SYSTEM_ERROR(statement, EINTR, message); \
193 func##_count = 0;
194 # define EXPECT_EQ_POSIX(expected, actual)
195 #endif
196
write_file(fmt::cstring_view filename,fmt::string_view content)197 static void write_file(fmt::cstring_view filename, fmt::string_view content) {
198 fmt::buffered_file f(filename, "w");
199 f.print("{}", content);
200 }
201
202 #if FMT_USE_FCNTL
203 using fmt::file;
204
TEST(UtilTest,GetPageSize)205 TEST(UtilTest, GetPageSize) {
206 # ifdef _WIN32
207 SYSTEM_INFO si = {};
208 GetSystemInfo(&si);
209 EXPECT_EQ(si.dwPageSize, fmt::getpagesize());
210 # else
211 EXPECT_EQ(sysconf(_SC_PAGESIZE), fmt::getpagesize());
212 sysconf_error = true;
213 EXPECT_SYSTEM_ERROR(fmt::getpagesize(), EINVAL,
214 "cannot get memory page size");
215 sysconf_error = false;
216 # endif
217 }
218
TEST(FileTest,OpenRetry)219 TEST(FileTest, OpenRetry) {
220 write_file("temp", "there must be something here");
221 std::unique_ptr<file> f{nullptr};
222 EXPECT_RETRY(f.reset(new file("temp", file::RDONLY)), open,
223 "cannot open file temp");
224 # ifndef _WIN32
225 char c = 0;
226 f->read(&c, 1);
227 # endif
228 }
229
TEST(FileTest,CloseNoRetryInDtor)230 TEST(FileTest, CloseNoRetryInDtor) {
231 file read_end, write_end;
232 file::pipe(read_end, write_end);
233 std::unique_ptr<file> f(new file(std::move(read_end)));
234 int saved_close_count = 0;
235 EXPECT_WRITE(
236 stderr,
237 {
238 close_count = 1;
239 f.reset(nullptr);
240 saved_close_count = close_count;
241 close_count = 0;
242 },
243 format_system_error(EINTR, "cannot close file") + "\n");
244 EXPECT_EQ(2, saved_close_count);
245 }
246
TEST(FileTest,CloseNoRetry)247 TEST(FileTest, CloseNoRetry) {
248 file read_end, write_end;
249 file::pipe(read_end, write_end);
250 close_count = 1;
251 EXPECT_SYSTEM_ERROR(read_end.close(), EINTR, "cannot close file");
252 EXPECT_EQ(2, close_count);
253 close_count = 0;
254 }
255
TEST(FileTest,Size)256 TEST(FileTest, Size) {
257 std::string content = "top secret, destroy before reading";
258 write_file("temp", content);
259 file f("temp", file::RDONLY);
260 EXPECT_GE(f.size(), 0);
261 EXPECT_EQ(content.size(), static_cast<unsigned long long>(f.size()));
262 # ifdef _WIN32
263 fmt::memory_buffer message;
264 fmt::detail::format_windows_error(message, ERROR_ACCESS_DENIED,
265 "cannot get file size");
266 fstat_sim = ERROR;
267 EXPECT_THROW_MSG(f.size(), fmt::windows_error, fmt::to_string(message));
268 fstat_sim = NONE;
269 # else
270 f.close();
271 EXPECT_SYSTEM_ERROR(f.size(), EBADF, "cannot get file attributes");
272 # endif
273 }
274
TEST(FileTest,MaxSize)275 TEST(FileTest, MaxSize) {
276 write_file("temp", "");
277 file f("temp", file::RDONLY);
278 fstat_sim = MAX_SIZE;
279 EXPECT_GE(f.size(), 0);
280 EXPECT_EQ(max_file_size(), f.size());
281 fstat_sim = NONE;
282 }
283
TEST(FileTest,ReadRetry)284 TEST(FileTest, ReadRetry) {
285 file read_end, write_end;
286 file::pipe(read_end, write_end);
287 enum { SIZE = 4 };
288 write_end.write("test", SIZE);
289 write_end.close();
290 char buffer[SIZE];
291 size_t count = 0;
292 EXPECT_RETRY(count = read_end.read(buffer, SIZE), read,
293 "cannot read from file");
294 EXPECT_EQ_POSIX(static_cast<std::streamsize>(SIZE), count);
295 }
296
TEST(FileTest,WriteRetry)297 TEST(FileTest, WriteRetry) {
298 file read_end, write_end;
299 file::pipe(read_end, write_end);
300 enum { SIZE = 4 };
301 size_t count = 0;
302 EXPECT_RETRY(count = write_end.write("test", SIZE), write,
303 "cannot write to file");
304 write_end.close();
305 # ifndef _WIN32
306 EXPECT_EQ(static_cast<std::streamsize>(SIZE), count);
307 char buffer[SIZE + 1];
308 read_end.read(buffer, SIZE);
309 buffer[SIZE] = '\0';
310 EXPECT_STREQ("test", buffer);
311 # endif
312 }
313
314 # ifdef _WIN32
TEST(FileTest,ConvertReadCount)315 TEST(FileTest, ConvertReadCount) {
316 file read_end, write_end;
317 file::pipe(read_end, write_end);
318 char c;
319 size_t size = UINT_MAX;
320 if (sizeof(unsigned) != sizeof(size_t)) ++size;
321 read_count = 1;
322 read_nbyte = 0;
323 EXPECT_THROW(read_end.read(&c, size), fmt::system_error);
324 read_count = 0;
325 EXPECT_EQ(UINT_MAX, read_nbyte);
326 }
327
TEST(FileTest,ConvertWriteCount)328 TEST(FileTest, ConvertWriteCount) {
329 file read_end, write_end;
330 file::pipe(read_end, write_end);
331 char c;
332 size_t size = UINT_MAX;
333 if (sizeof(unsigned) != sizeof(size_t)) ++size;
334 write_count = 1;
335 write_nbyte = 0;
336 EXPECT_THROW(write_end.write(&c, size), fmt::system_error);
337 write_count = 0;
338 EXPECT_EQ(UINT_MAX, write_nbyte);
339 }
340 # endif
341
TEST(FileTest,DupNoRetry)342 TEST(FileTest, DupNoRetry) {
343 int stdout_fd = FMT_POSIX(fileno(stdout));
344 dup_count = 1;
345 EXPECT_SYSTEM_ERROR(
346 file::dup(stdout_fd), EINTR,
347 fmt::format("cannot duplicate file descriptor {}", stdout_fd));
348 dup_count = 0;
349 }
350
TEST(FileTest,Dup2Retry)351 TEST(FileTest, Dup2Retry) {
352 int stdout_fd = FMT_POSIX(fileno(stdout));
353 file f1 = file::dup(stdout_fd), f2 = file::dup(stdout_fd);
354 EXPECT_RETRY(f1.dup2(f2.descriptor()), dup2,
355 fmt::format("cannot duplicate file descriptor {} to {}",
356 f1.descriptor(), f2.descriptor()));
357 }
358
TEST(FileTest,Dup2NoExceptRetry)359 TEST(FileTest, Dup2NoExceptRetry) {
360 int stdout_fd = FMT_POSIX(fileno(stdout));
361 file f1 = file::dup(stdout_fd), f2 = file::dup(stdout_fd);
362 error_code ec;
363 dup2_count = 1;
364 f1.dup2(f2.descriptor(), ec);
365 # ifndef _WIN32
366 EXPECT_EQ(4, dup2_count);
367 # else
368 EXPECT_EQ(EINTR, ec.get());
369 # endif
370 dup2_count = 0;
371 }
372
TEST(FileTest,PipeNoRetry)373 TEST(FileTest, PipeNoRetry) {
374 file read_end, write_end;
375 pipe_count = 1;
376 EXPECT_SYSTEM_ERROR(file::pipe(read_end, write_end), EINTR,
377 "cannot create pipe");
378 pipe_count = 0;
379 }
380
TEST(FileTest,FdopenNoRetry)381 TEST(FileTest, FdopenNoRetry) {
382 file read_end, write_end;
383 file::pipe(read_end, write_end);
384 fdopen_count = 1;
385 EXPECT_SYSTEM_ERROR(read_end.fdopen("r"), EINTR,
386 "cannot associate stream with file descriptor");
387 fdopen_count = 0;
388 }
389
TEST(BufferedFileTest,OpenRetry)390 TEST(BufferedFileTest, OpenRetry) {
391 write_file("temp", "there must be something here");
392 std::unique_ptr<buffered_file> f{nullptr};
393 EXPECT_RETRY(f.reset(new buffered_file("temp", "r")), fopen,
394 "cannot open file temp");
395 # ifndef _WIN32
396 char c = 0;
397 if (fread(&c, 1, 1, f->get()) < 1)
398 throw fmt::system_error(errno, "fread failed");
399 # endif
400 }
401
TEST(BufferedFileTest,CloseNoRetryInDtor)402 TEST(BufferedFileTest, CloseNoRetryInDtor) {
403 file read_end, write_end;
404 file::pipe(read_end, write_end);
405 std::unique_ptr<buffered_file> f(new buffered_file(read_end.fdopen("r")));
406 int saved_fclose_count = 0;
407 EXPECT_WRITE(
408 stderr,
409 {
410 fclose_count = 1;
411 f.reset(nullptr);
412 saved_fclose_count = fclose_count;
413 fclose_count = 0;
414 },
415 format_system_error(EINTR, "cannot close file") + "\n");
416 EXPECT_EQ(2, saved_fclose_count);
417 }
418
TEST(BufferedFileTest,CloseNoRetry)419 TEST(BufferedFileTest, CloseNoRetry) {
420 file read_end, write_end;
421 file::pipe(read_end, write_end);
422 buffered_file f = read_end.fdopen("r");
423 fclose_count = 1;
424 EXPECT_SYSTEM_ERROR(f.close(), EINTR, "cannot close file");
425 EXPECT_EQ(2, fclose_count);
426 fclose_count = 0;
427 }
428
TEST(BufferedFileTest,FilenoNoRetry)429 TEST(BufferedFileTest, FilenoNoRetry) {
430 file read_end, write_end;
431 file::pipe(read_end, write_end);
432 buffered_file f = read_end.fdopen("r");
433 fileno_count = 1;
434 EXPECT_SYSTEM_ERROR((f.fileno)(), EINTR, "cannot get file descriptor");
435 EXPECT_EQ(2, fileno_count);
436 fileno_count = 0;
437 }
438 #endif // FMT_USE_FCNTL
439
440 struct test_mock {
441 static test_mock* instance;
442 } * test_mock::instance;
443
TEST(ScopedMock,Scope)444 TEST(ScopedMock, Scope) {
445 {
446 ScopedMock<test_mock> mock;
447 EXPECT_EQ(&mock, test_mock::instance);
448 test_mock& copy = mock;
449 static_cast<void>(copy);
450 }
451 EXPECT_EQ(nullptr, test_mock::instance);
452 }
453
454 #ifdef FMT_LOCALE
455
456 typedef fmt::locale::type locale_type;
457
458 struct locale_mock {
459 static locale_mock* instance;
460 MOCK_METHOD3(newlocale, locale_type(int category_mask, const char* locale,
461 locale_type base));
462 MOCK_METHOD1(freelocale, void(locale_type locale));
463
464 MOCK_METHOD3(strtod_l,
465 double(const char* nptr, char** endptr, locale_type locale));
466 } * locale_mock::instance;
467
468 # ifdef _MSC_VER
469 # pragma warning(push)
470 # pragma warning(disable : 4273)
471 # ifdef __clang__
472 # pragma clang diagnostic push
473 # pragma clang diagnostic ignored "-Winconsistent-dllimport"
474 # endif
475
_create_locale(int category,const char * locale)476 _locale_t _create_locale(int category, const char* locale) {
477 return locale_mock::instance->newlocale(category, locale, 0);
478 }
479
_free_locale(_locale_t locale)480 void _free_locale(_locale_t locale) {
481 locale_mock::instance->freelocale(locale);
482 }
483
_strtod_l(const char * nptr,char ** endptr,_locale_t locale)484 double _strtod_l(const char* nptr, char** endptr, _locale_t locale) {
485 return locale_mock::instance->strtod_l(nptr, endptr, locale);
486 }
487 # ifdef __clang__
488 # pragma clang diagnostic pop
489 # endif
490 # pragma warning(pop)
491 # endif
492
493 # if defined(__THROW) && FMT_GCC_VERSION > 0 && FMT_GCC_VERSION <= 408
494 # define FMT_LOCALE_THROW __THROW
495 # else
496 # define FMT_LOCALE_THROW
497 # endif
498
499 # if defined(__APPLE__) || \
500 (defined(__FreeBSD__) && __FreeBSD_version < 1200002)
501 typedef int FreeLocaleResult;
502 # else
503 typedef void FreeLocaleResult;
504 # endif
505
freelocale(locale_type locale)506 FreeLocaleResult freelocale(locale_type locale) FMT_LOCALE_THROW {
507 locale_mock::instance->freelocale(locale);
508 return FreeLocaleResult();
509 }
510
strtod_l(const char * nptr,char ** endptr,locale_type locale)511 double strtod_l(const char* nptr, char** endptr,
512 locale_type locale) FMT_LOCALE_THROW {
513 return locale_mock::instance->strtod_l(nptr, endptr, locale);
514 }
515
516 # undef FMT_LOCALE_THROW
517
518 # ifndef _WIN32
newlocale(int category_mask,const char * locale,locale_t base)519 locale_t test::newlocale(int category_mask, const char* locale, locale_t base) {
520 return locale_mock::instance->newlocale(category_mask, locale, base);
521 }
522
TEST(LocaleTest,LocaleMock)523 TEST(LocaleTest, LocaleMock) {
524 ScopedMock<locale_mock> mock;
525 locale_type locale = reinterpret_cast<locale_type>(11);
526 EXPECT_CALL(mock, newlocale(222, StrEq("foo"), locale));
527 FMT_SYSTEM(newlocale(222, "foo", locale));
528 }
529 # endif
530
TEST(LocaleTest,Locale)531 TEST(LocaleTest, Locale) {
532 # ifndef LC_NUMERIC_MASK
533 enum { LC_NUMERIC_MASK = LC_NUMERIC };
534 # endif
535 ScopedMock<locale_mock> mock;
536 locale_type impl = reinterpret_cast<locale_type>(42);
537 EXPECT_CALL(mock, newlocale(LC_NUMERIC_MASK, StrEq("C"), nullptr))
538 .WillOnce(Return(impl));
539 EXPECT_CALL(mock, freelocale(impl));
540 fmt::locale loc;
541 EXPECT_EQ(impl, loc.get());
542 }
543
TEST(LocaleTest,Strtod)544 TEST(LocaleTest, Strtod) {
545 ScopedMock<locale_mock> mock;
546 EXPECT_CALL(mock, newlocale(_, _, _))
547 .WillOnce(Return(reinterpret_cast<locale_type>(42)));
548 EXPECT_CALL(mock, freelocale(_));
549 fmt::locale loc;
550 const char* str = "4.2";
551 char end = 'x';
552 EXPECT_CALL(mock, strtod_l(str, _, loc.get()))
553 .WillOnce(testing::DoAll(testing::SetArgPointee<1>(&end), Return(777)));
554 EXPECT_EQ(777, loc.strtod(str));
555 EXPECT_EQ(&end, str);
556 }
557
558 #endif // FMT_LOCALE
559