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