1 #ifndef FILESYSTEM_TEST_HELPER_H
2 #define FILESYSTEM_TEST_HELPER_H
3
4 #include "filesystem_include.h"
5
6 #include <sys/stat.h> // for stat, mkdir, mkfifo
7 #ifndef _WIN32
8 #include <unistd.h> // for ftruncate, link, symlink, getcwd, chdir
9 #include <sys/statvfs.h>
10 #else
11 #include <io.h>
12 #include <direct.h>
13 #include <windows.h> // for CreateSymbolicLink, CreateHardLink
14 #endif
15
16 #include <cassert>
17 #include <cstdio> // for printf
18 #include <string>
19 #include <chrono>
20 #include <vector>
21
22 #include "test_macros.h"
23 #include "rapid-cxx-test.h"
24 #include "format_string.h"
25
26 // For creating socket files
27 #if !defined(__FreeBSD__) && !defined(__APPLE__) && !defined(_WIN32)
28 # include <sys/socket.h>
29 # include <sys/un.h>
30 #endif
31
32 namespace utils {
33 #ifdef _WIN32
mkdir(const char * path,int mode)34 inline int mkdir(const char* path, int mode) { (void)mode; return ::_mkdir(path); }
ftruncate(int fd,off_t length)35 inline int ftruncate(int fd, off_t length) { return ::_chsize(fd, length); }
symlink(const char * oldname,const char * newname,bool is_dir)36 inline int symlink(const char* oldname, const char* newname, bool is_dir) {
37 DWORD flags = is_dir ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0;
38 if (CreateSymbolicLinkA(newname, oldname,
39 flags | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE))
40 return 0;
41 if (GetLastError() != ERROR_INVALID_PARAMETER)
42 return 1;
43 return !CreateSymbolicLinkA(newname, oldname, flags);
44 }
link(const char * oldname,const char * newname)45 inline int link(const char *oldname, const char* newname) {
46 return !CreateHardLinkA(newname, oldname, NULL);
47 }
setenv(const char * var,const char * val,int overwrite)48 inline int setenv(const char *var, const char *val, int overwrite) {
49 (void)overwrite;
50 return ::_putenv((std::string(var) + "=" + std::string(val)).c_str());
51 }
unsetenv(const char * var)52 inline int unsetenv(const char *var) {
53 return ::_putenv((std::string(var) + "=").c_str());
54 }
space(std::string path,std::uintmax_t & capacity,std::uintmax_t & free,std::uintmax_t & avail)55 inline bool space(std::string path, std::uintmax_t &capacity,
56 std::uintmax_t &free, std::uintmax_t &avail) {
57 ULARGE_INTEGER FreeBytesAvailableToCaller, TotalNumberOfBytes,
58 TotalNumberOfFreeBytes;
59 if (!GetDiskFreeSpaceExA(path.c_str(), &FreeBytesAvailableToCaller,
60 &TotalNumberOfBytes, &TotalNumberOfFreeBytes))
61 return false;
62 capacity = TotalNumberOfBytes.QuadPart;
63 free = TotalNumberOfFreeBytes.QuadPart;
64 avail = FreeBytesAvailableToCaller.QuadPart;
65 assert(capacity > 0);
66 assert(free > 0);
67 assert(avail > 0);
68 return true;
69 }
70 #else
71 using ::mkdir;
72 using ::ftruncate;
73 inline int symlink(const char* oldname, const char* newname, bool is_dir) { (void)is_dir; return ::symlink(oldname, newname); }
74 using ::link;
75 using ::setenv;
76 using ::unsetenv;
77 inline bool space(std::string path, std::uintmax_t &capacity,
78 std::uintmax_t &free, std::uintmax_t &avail) {
79 struct statvfs expect;
80 if (::statvfs(path.c_str(), &expect) == -1)
81 return false;
82 assert(expect.f_bavail > 0);
83 assert(expect.f_bfree > 0);
84 assert(expect.f_bsize > 0);
85 assert(expect.f_blocks > 0);
86 assert(expect.f_frsize > 0);
87 auto do_mult = [&](std::uintmax_t val) {
88 std::uintmax_t fsize = expect.f_frsize;
89 std::uintmax_t new_val = val * fsize;
90 assert(new_val / fsize == val); // Test for overflow
91 return new_val;
92 };
93 capacity = do_mult(expect.f_blocks);
94 free = do_mult(expect.f_bfree);
95 avail = do_mult(expect.f_bavail);
96 return true;
97 }
98 #endif
99
getcwd()100 inline std::string getcwd() {
101 // Assume that path lengths are not greater than this.
102 // This should be fine for testing purposes.
103 char buf[4096];
104 char* ret = ::getcwd(buf, sizeof(buf));
105 assert(ret && "getcwd failed");
106 return std::string(ret);
107 }
108
exists(std::string const & path)109 inline bool exists(std::string const& path) {
110 struct ::stat tmp;
111 return ::stat(path.c_str(), &tmp) == 0;
112 }
113 } // end namespace utils
114
115 struct scoped_test_env
116 {
scoped_test_envscoped_test_env117 scoped_test_env() : test_root(available_cwd_path()) {
118 #ifdef _WIN32
119 // Windows mkdir can create multiple recursive directories
120 // if needed.
121 std::string cmd = "mkdir " + test_root.string();
122 #else
123 std::string cmd = "mkdir -p " + test_root.string();
124 #endif
125 int ret = std::system(cmd.c_str());
126 assert(ret == 0);
127
128 // Ensure that the root_path is fully resolved, i.e. it contains no
129 // symlinks. The filesystem tests depend on that. We do this after
130 // creating the root_path, because `fs::canonical` requires the
131 // path to exist.
132 test_root = fs::canonical(test_root);
133 }
134
~scoped_test_envscoped_test_env135 ~scoped_test_env() {
136 #ifdef _WIN32
137 std::string cmd = "rmdir /s /q " + test_root.string();
138 int ret = std::system(cmd.c_str());
139 assert(ret == 0);
140 #else
141 std::string cmd = "chmod -R 777 " + test_root.string();
142 int ret = std::system(cmd.c_str());
143 assert(ret == 0);
144
145 cmd = "rm -r " + test_root.string();
146 ret = std::system(cmd.c_str());
147 assert(ret == 0);
148 #endif
149 }
150
151 scoped_test_env(scoped_test_env const &) = delete;
152 scoped_test_env & operator=(scoped_test_env const &) = delete;
153
make_env_pathscoped_test_env154 fs::path make_env_path(std::string p) { return sanitize_path(p); }
155
sanitize_pathscoped_test_env156 std::string sanitize_path(std::string raw) {
157 assert(raw.find("..") == std::string::npos);
158 std::string root = test_root.string();
159 if (root.compare(0, root.size(), raw, 0, root.size()) != 0) {
160 assert(raw.front() != '\\');
161 fs::path tmp(test_root);
162 tmp /= raw;
163 return tmp.string();
164 }
165 return raw;
166 }
167
168 // Purposefully using a size potentially larger than off_t here so we can
169 // test the behavior of libc++fs when it is built with _FILE_OFFSET_BITS=64
170 // but the caller is not (std::filesystem also uses uintmax_t rather than
171 // off_t). On a 32-bit system this allows us to create a file larger than
172 // 2GB.
173 std::string create_file(fs::path filename_path, uintmax_t size = 0) {
174 std::string filename = filename_path.string();
175 #if defined(__LP64__) || defined(_WIN32)
176 auto large_file_fopen = fopen;
177 auto large_file_ftruncate = utils::ftruncate;
178 using large_file_offset_t = off_t;
179 #else
180 auto large_file_fopen = fopen64;
181 auto large_file_ftruncate = ftruncate64;
182 using large_file_offset_t = off64_t;
183 #endif
184
185 filename = sanitize_path(std::move(filename));
186
187 if (size >
188 static_cast<typename std::make_unsigned<large_file_offset_t>::type>(
189 std::numeric_limits<large_file_offset_t>::max())) {
190 fprintf(stderr, "create_file(%s, %ju) too large\n",
191 filename.c_str(), size);
192 abort();
193 }
194
195 #ifndef _WIN32
196 #define FOPEN_CLOEXEC_FLAG "e"
197 #else
198 #define FOPEN_CLOEXEC_FLAG ""
199 #endif
200 FILE* file = large_file_fopen(filename.c_str(), "w" FOPEN_CLOEXEC_FLAG);
201 if (file == nullptr) {
202 fprintf(stderr, "fopen %s failed: %s\n", filename.c_str(),
203 strerror(errno));
204 abort();
205 }
206
207 if (large_file_ftruncate(
208 fileno(file), static_cast<large_file_offset_t>(size)) == -1) {
209 fprintf(stderr, "ftruncate %s %ju failed: %s\n", filename.c_str(),
210 size, strerror(errno));
211 fclose(file);
212 abort();
213 }
214
215 fclose(file);
216 return filename;
217 }
218
create_dirscoped_test_env219 std::string create_dir(fs::path filename_path) {
220 std::string filename = filename_path.string();
221 filename = sanitize_path(std::move(filename));
222 int ret = utils::mkdir(filename.c_str(), 0777); // rwxrwxrwx mode
223 assert(ret == 0);
224 return filename;
225 }
226
227 std::string create_file_dir_symlink(fs::path source_path,
228 fs::path to_path,
229 bool sanitize_source = true,
230 bool is_dir = false) {
231 std::string source = source_path.string();
232 std::string to = to_path.string();
233 if (sanitize_source)
234 source = sanitize_path(std::move(source));
235 to = sanitize_path(std::move(to));
236 int ret = utils::symlink(source.c_str(), to.c_str(), is_dir);
237 assert(ret == 0);
238 return to;
239 }
240
241 std::string create_symlink(fs::path source_path,
242 fs::path to_path,
243 bool sanitize_source = true) {
244 return create_file_dir_symlink(source_path, to_path, sanitize_source,
245 false);
246 }
247
248 std::string create_directory_symlink(fs::path source_path,
249 fs::path to_path,
250 bool sanitize_source = true) {
251 return create_file_dir_symlink(source_path, to_path, sanitize_source,
252 true);
253 }
254
create_hardlinkscoped_test_env255 std::string create_hardlink(fs::path source_path, fs::path to_path) {
256 std::string source = source_path.string();
257 std::string to = to_path.string();
258 source = sanitize_path(std::move(source));
259 to = sanitize_path(std::move(to));
260 int ret = utils::link(source.c_str(), to.c_str());
261 assert(ret == 0);
262 return to;
263 }
264
265 #ifndef _WIN32
create_fifoscoped_test_env266 std::string create_fifo(std::string file) {
267 file = sanitize_path(std::move(file));
268 int ret = ::mkfifo(file.c_str(), 0666); // rw-rw-rw- mode
269 assert(ret == 0);
270 return file;
271 }
272 #endif
273
274 // Some platforms doesn't support socket files so we shouldn't even
275 // allow tests to call this unguarded.
276 #if !defined(__FreeBSD__) && !defined(__APPLE__) && !defined(_WIN32)
create_socketscoped_test_env277 std::string create_socket(std::string file) {
278 file = sanitize_path(std::move(file));
279
280 ::sockaddr_un address;
281 address.sun_family = AF_UNIX;
282 assert(file.size() <= sizeof(address.sun_path));
283 ::strncpy(address.sun_path, file.c_str(), sizeof(address.sun_path));
284 int fd = ::socket(AF_UNIX, SOCK_STREAM, 0);
285 ::bind(fd, reinterpret_cast<::sockaddr*>(&address), sizeof(address));
286 return file;
287 }
288 #endif
289
290 fs::path test_root;
291
292 private:
293 // This could potentially introduce a filesystem race if multiple
294 // scoped_test_envs were created concurrently in the same test (hence
295 // sharing the same cwd). However, it is fairly unlikely to happen as
296 // we generally don't use scoped_test_env from multiple threads, so
297 // this is deemed acceptable.
available_cwd_pathscoped_test_env298 static inline fs::path available_cwd_path() {
299 fs::path const cwd = utils::getcwd();
300 fs::path const tmp = fs::temp_directory_path();
301 fs::path const base = tmp / cwd.filename();
302 int i = 0;
303 fs::path p = base / ("static_env." + std::to_string(i));
304 while (utils::exists(p.string())) {
305 p = fs::path(base) / ("static_env." + std::to_string(++i));
306 }
307 return p;
308 }
309 };
310
311 /// This class generates the following tree:
312 ///
313 /// static_test_env
314 /// ├── bad_symlink -> dne
315 /// ├── dir1
316 /// │ ├── dir2
317 /// │ │ ├── afile3
318 /// │ │ ├── dir3
319 /// │ │ │ └── file5
320 /// │ │ ├── file4
321 /// │ │ └── symlink_to_dir3 -> dir3
322 /// │ ├── file1
323 /// │ └── file2
324 /// ├── empty_file
325 /// ├── non_empty_file
326 /// ├── symlink_to_dir -> dir1
327 /// └── symlink_to_empty_file -> empty_file
328 ///
329 class static_test_env {
330 scoped_test_env env_;
331 public:
static_test_env()332 static_test_env() {
333 env_.create_symlink("dne", "bad_symlink", false);
334 env_.create_dir("dir1");
335 env_.create_dir("dir1/dir2");
336 env_.create_file("dir1/dir2/afile3");
337 env_.create_dir("dir1/dir2/dir3");
338 env_.create_file("dir1/dir2/dir3/file5");
339 env_.create_file("dir1/dir2/file4");
340 env_.create_directory_symlink("dir3", "dir1/dir2/symlink_to_dir3", false);
341 env_.create_file("dir1/file1");
342 env_.create_file("dir1/file2", 42);
343 env_.create_file("empty_file");
344 env_.create_file("non_empty_file", 42);
345 env_.create_directory_symlink("dir1", "symlink_to_dir", false);
346 env_.create_symlink("empty_file", "symlink_to_empty_file", false);
347 }
348
349 const fs::path Root = env_.test_root;
350
makePath(fs::path const & p)351 fs::path makePath(fs::path const& p) const {
352 // env_path is expected not to contain symlinks.
353 fs::path const& env_path = Root;
354 return env_path / p;
355 }
356
357 const std::vector<fs::path> TestFileList = {
358 makePath("empty_file"),
359 makePath("non_empty_file"),
360 makePath("dir1/file1"),
361 makePath("dir1/file2")
362 };
363
364 const std::vector<fs::path> TestDirList = {
365 makePath("dir1"),
366 makePath("dir1/dir2"),
367 makePath("dir1/dir2/dir3")
368 };
369
370 const fs::path File = TestFileList[0];
371 const fs::path Dir = TestDirList[0];
372 const fs::path Dir2 = TestDirList[1];
373 const fs::path Dir3 = TestDirList[2];
374 const fs::path SymlinkToFile = makePath("symlink_to_empty_file");
375 const fs::path SymlinkToDir = makePath("symlink_to_dir");
376 const fs::path BadSymlink = makePath("bad_symlink");
377 const fs::path DNE = makePath("DNE");
378 const fs::path EmptyFile = TestFileList[0];
379 const fs::path NonEmptyFile = TestFileList[1];
380 const fs::path CharFile = "/dev/null"; // Hopefully this exists
381
382 const std::vector<fs::path> DirIterationList = {
383 makePath("dir1/dir2"),
384 makePath("dir1/file1"),
385 makePath("dir1/file2")
386 };
387
388 const std::vector<fs::path> DirIterationListDepth1 = {
389 makePath("dir1/dir2/afile3"),
390 makePath("dir1/dir2/dir3"),
391 makePath("dir1/dir2/symlink_to_dir3"),
392 makePath("dir1/dir2/file4"),
393 };
394
395 const std::vector<fs::path> RecDirIterationList = {
396 makePath("dir1/dir2"),
397 makePath("dir1/file1"),
398 makePath("dir1/file2"),
399 makePath("dir1/dir2/afile3"),
400 makePath("dir1/dir2/dir3"),
401 makePath("dir1/dir2/symlink_to_dir3"),
402 makePath("dir1/dir2/file4"),
403 makePath("dir1/dir2/dir3/file5")
404 };
405
406 const std::vector<fs::path> RecDirFollowSymlinksIterationList = {
407 makePath("dir1/dir2"),
408 makePath("dir1/file1"),
409 makePath("dir1/file2"),
410 makePath("dir1/dir2/afile3"),
411 makePath("dir1/dir2/dir3"),
412 makePath("dir1/dir2/file4"),
413 makePath("dir1/dir2/dir3/file5"),
414 makePath("dir1/dir2/symlink_to_dir3"),
415 makePath("dir1/dir2/symlink_to_dir3/file5"),
416 };
417 };
418
419 struct CWDGuard {
420 std::string oldCwd_;
CWDGuardCWDGuard421 CWDGuard() : oldCwd_(utils::getcwd()) { }
~CWDGuardCWDGuard422 ~CWDGuard() {
423 int ret = ::chdir(oldCwd_.c_str());
424 assert(ret == 0 && "chdir failed");
425 }
426
427 CWDGuard(CWDGuard const&) = delete;
428 CWDGuard& operator=(CWDGuard const&) = delete;
429 };
430
431 // Misc test types
432
433 #if TEST_STD_VER > 17 && defined(__cpp_char8_t)
434 #define CHAR8_ONLY(x) x,
435 #else
436 #define CHAR8_ONLY(x)
437 #endif
438
439 #define MKSTR(Str) {Str, TEST_CONCAT(L, Str), CHAR8_ONLY(TEST_CONCAT(u8, Str)) TEST_CONCAT(u, Str), TEST_CONCAT(U, Str)}
440
441 struct MultiStringType {
442 const char* s;
443 const wchar_t* w;
444 #if TEST_STD_VER > 17 && defined(__cpp_char8_t)
445 const char8_t* u8;
446 #endif
447 const char16_t* u16;
448 const char32_t* u32;
449
450 operator const char* () const { return s; }
451 operator const wchar_t* () const { return w; }
452 #if TEST_STD_VER > 17 && defined(__cpp_char8_t)
453 operator const char8_t* () const { return u8; }
454 #endif
455 operator const char16_t* () const { return u16; }
456 operator const char32_t* () const { return u32; }
457 };
458
459 const MultiStringType PathList[] = {
460 MKSTR(""),
461 MKSTR(" "),
462 MKSTR("//"),
463 MKSTR("."),
464 MKSTR(".."),
465 MKSTR("foo"),
466 MKSTR("/"),
467 MKSTR("/foo"),
468 MKSTR("foo/"),
469 MKSTR("/foo/"),
470 MKSTR("foo/bar"),
471 MKSTR("/foo/bar"),
472 MKSTR("//net"),
473 MKSTR("//net/foo"),
474 MKSTR("///foo///"),
475 MKSTR("///foo///bar"),
476 MKSTR("/."),
477 MKSTR("./"),
478 MKSTR("/.."),
479 MKSTR("../"),
480 MKSTR("foo/."),
481 MKSTR("foo/.."),
482 MKSTR("foo/./"),
483 MKSTR("foo/./bar"),
484 MKSTR("foo/../"),
485 MKSTR("foo/../bar"),
486 MKSTR("c:"),
487 MKSTR("c:/"),
488 MKSTR("c:foo"),
489 MKSTR("c:/foo"),
490 MKSTR("c:foo/"),
491 MKSTR("c:/foo/"),
492 MKSTR("c:/foo/bar"),
493 MKSTR("prn:"),
494 MKSTR("c:\\"),
495 MKSTR("c:\\foo"),
496 MKSTR("c:foo\\"),
497 MKSTR("c:\\foo\\"),
498 MKSTR("c:\\foo/"),
499 MKSTR("c:/foo\\bar"),
500 MKSTR("//"),
501 MKSTR("/finally/we/need/one/really/really/really/really/really/really/really/long/string")
502 };
503 const unsigned PathListSize = sizeof(PathList) / sizeof(MultiStringType);
504
505 template <class Iter>
IterEnd(Iter B)506 Iter IterEnd(Iter B) {
507 using VT = typename std::iterator_traits<Iter>::value_type;
508 for (; *B != VT{}; ++B)
509 ;
510 return B;
511 }
512
513 template <class CharT>
StrEnd(CharT const * P)514 const CharT* StrEnd(CharT const* P) {
515 return IterEnd(P);
516 }
517
518 template <class CharT>
StrLen(CharT const * P)519 std::size_t StrLen(CharT const* P) {
520 return StrEnd(P) - P;
521 }
522
523 // Testing the allocation behavior of the code_cvt functions requires
524 // *knowing* that the allocation was not done by "path::__str_".
525 // This hack forces path to allocate enough memory.
PathReserve(fs::path & p,std::size_t N)526 inline void PathReserve(fs::path& p, std::size_t N) {
527 auto const& native_ref = p.native();
528 const_cast<fs::path::string_type&>(native_ref).reserve(N);
529 }
530
531 template <class Iter1, class Iter2>
checkCollectionsEqual(Iter1 start1,Iter1 const end1,Iter2 start2,Iter2 const end2)532 bool checkCollectionsEqual(
533 Iter1 start1, Iter1 const end1
534 , Iter2 start2, Iter2 const end2
535 )
536 {
537 while (start1 != end1 && start2 != end2) {
538 if (*start1 != *start2) {
539 return false;
540 }
541 ++start1; ++start2;
542 }
543 return (start1 == end1 && start2 == end2);
544 }
545
546
547 template <class Iter1, class Iter2>
checkCollectionsEqualBackwards(Iter1 const start1,Iter1 end1,Iter2 const start2,Iter2 end2)548 bool checkCollectionsEqualBackwards(
549 Iter1 const start1, Iter1 end1
550 , Iter2 const start2, Iter2 end2
551 )
552 {
553 while (start1 != end1 && start2 != end2) {
554 --end1; --end2;
555 if (*end1 != *end2) {
556 return false;
557 }
558 }
559 return (start1 == end1 && start2 == end2);
560 }
561
562 // We often need to test that the error_code was cleared if no error occurs
563 // this function returns an error_code which is set to an error that will
564 // never be returned by the filesystem functions.
565 inline std::error_code GetTestEC(unsigned Idx = 0) {
566 using std::errc;
567 auto GetErrc = [&]() {
568 switch (Idx) {
569 case 0:
570 return errc::address_family_not_supported;
571 case 1:
572 return errc::address_not_available;
573 case 2:
574 return errc::address_in_use;
575 case 3:
576 return errc::argument_list_too_long;
577 default:
578 assert(false && "Idx out of range");
579 std::abort();
580 }
581 };
582 return std::make_error_code(GetErrc());
583 }
584
ErrorIsImp(const std::error_code & ec,std::vector<std::errc> const & errors)585 inline bool ErrorIsImp(const std::error_code& ec,
586 std::vector<std::errc> const& errors) {
587 std::error_condition cond = ec.default_error_condition();
588 for (auto errc : errors) {
589 if (cond.value() == static_cast<int>(errc))
590 return true;
591 }
592 return false;
593 }
594
595 template <class... ErrcT>
ErrorIs(const std::error_code & ec,std::errc First,ErrcT...Rest)596 inline bool ErrorIs(const std::error_code& ec, std::errc First, ErrcT... Rest) {
597 std::vector<std::errc> errors = {First, Rest...};
598 return ErrorIsImp(ec, errors);
599 }
600
601 // Provide our own Sleep routine since std::this_thread::sleep_for is not
602 // available in single-threaded mode.
SleepFor(std::chrono::seconds dur)603 void SleepFor(std::chrono::seconds dur) {
604 using namespace std::chrono;
605 #if defined(_LIBCPP_HAS_NO_MONOTONIC_CLOCK)
606 using Clock = system_clock;
607 #else
608 using Clock = steady_clock;
609 #endif
610 const auto wake_time = Clock::now() + dur;
611 while (Clock::now() < wake_time)
612 ;
613 }
614
PathEq(fs::path const & LHS,fs::path const & RHS)615 inline bool PathEq(fs::path const& LHS, fs::path const& RHS) {
616 return LHS.native() == RHS.native();
617 }
618
619 struct ExceptionChecker {
620 std::errc expected_err;
621 fs::path expected_path1;
622 fs::path expected_path2;
623 unsigned num_paths;
624 const char* func_name;
625 std::string opt_message;
626
627 explicit ExceptionChecker(std::errc first_err, const char* fun_name,
628 std::string opt_msg = {})
629 : expected_err{first_err}, num_paths(0), func_name(fun_name),
630 opt_message(opt_msg) {}
631 explicit ExceptionChecker(fs::path p, std::errc first_err,
632 const char* fun_name, std::string opt_msg = {})
expected_errExceptionChecker633 : expected_err(first_err), expected_path1(p), num_paths(1),
634 func_name(fun_name), opt_message(opt_msg) {}
635
636 explicit ExceptionChecker(fs::path p1, fs::path p2, std::errc first_err,
637 const char* fun_name, std::string opt_msg = {})
expected_errExceptionChecker638 : expected_err(first_err), expected_path1(p1), expected_path2(p2),
639 num_paths(2), func_name(fun_name), opt_message(opt_msg) {}
640
operatorExceptionChecker641 void operator()(fs::filesystem_error const& Err) {
642 TEST_CHECK(ErrorIsImp(Err.code(), {expected_err}));
643 TEST_CHECK(Err.path1() == expected_path1);
644 TEST_CHECK(Err.path2() == expected_path2);
645 LIBCPP_ONLY(check_libcxx_string(Err));
646 }
647
check_libcxx_stringExceptionChecker648 void check_libcxx_string(fs::filesystem_error const& Err) {
649 std::string message = std::make_error_code(expected_err).message();
650
651 std::string additional_msg = "";
652 if (!opt_message.empty()) {
653 additional_msg = opt_message + ": ";
654 }
655 auto transform_path = [](const fs::path& p) {
656 if (p.native().empty())
657 return std::string("\"\"");
658 return p.string();
659 };
660 std::string format = [&]() -> std::string {
661 switch (num_paths) {
662 case 0:
663 return format_string("filesystem error: in %s: %s%s", func_name,
664 additional_msg, message);
665 case 1:
666 return format_string("filesystem error: in %s: %s%s [%s]", func_name,
667 additional_msg, message,
668 transform_path(expected_path1).c_str());
669 case 2:
670 return format_string("filesystem error: in %s: %s%s [%s] [%s]",
671 func_name, additional_msg, message,
672 transform_path(expected_path1).c_str(),
673 transform_path(expected_path2).c_str());
674 default:
675 TEST_CHECK(false && "unexpected case");
676 return "";
677 }
678 }();
679 TEST_CHECK(format == Err.what());
680 if (format != Err.what()) {
681 fprintf(stderr,
682 "filesystem_error::what() does not match expected output:\n");
683 fprintf(stderr, " expected: \"%s\"\n", format.c_str());
684 fprintf(stderr, " actual: \"%s\"\n\n", Err.what());
685 }
686 }
687
688 ExceptionChecker(ExceptionChecker const&) = delete;
689 ExceptionChecker& operator=(ExceptionChecker const&) = delete;
690
691 };
692
693 #endif /* FILESYSTEM_TEST_HELPER_HPP */
694