1 #ifndef FILESYSTEM_TEST_HELPER_H
2 #define FILESYSTEM_TEST_HELPER_H
4 #include "filesystem_include.h"
6 #include <sys/stat.h> // for stat, mkdir, mkfifo
8 #include <unistd.h> // for ftruncate, link, symlink, getcwd, chdir
9 #include <sys/statvfs.h>
13 #include <windows.h> // for CreateSymbolicLink, CreateHardLink
17 #include <cstdio> // for printf
22 #include "test_macros.h"
23 #include "rapid-cxx-test.h"
24 #include "format_string.h"
26 // For creating socket files
27 #if !defined(__FreeBSD__) && !defined(__APPLE__) && !defined(_WIN32)
28 # include <sys/socket.h>
34 inline int mkdir(const char* path
, int mode
) { (void)mode
; return ::_mkdir(path
); }
35 inline int ftruncate(int fd
, off_t length
) { return ::_chsize(fd
, length
); }
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
))
41 if (GetLastError() != ERROR_INVALID_PARAMETER
)
43 return !CreateSymbolicLinkA(newname
, oldname
, flags
);
45 inline int link(const char *oldname
, const char* newname
) {
46 return !CreateHardLinkA(newname
, oldname
, NULL
);
48 inline int setenv(const char *var
, const char *val
, int overwrite
) {
50 return ::_putenv((std::string(var
) + "=" + std::string(val
)).c_str());
52 inline int unsetenv(const char *var
) {
53 return ::_putenv((std::string(var
) + "=").c_str());
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
))
62 capacity
= TotalNumberOfBytes
.QuadPart
;
63 free
= TotalNumberOfFreeBytes
.QuadPart
;
64 avail
= FreeBytesAvailableToCaller
.QuadPart
;
73 inline int symlink(const char* oldname
, const char* newname
, bool is_dir
) { (void)is_dir
; return ::symlink(oldname
, newname
); }
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)
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
93 capacity
= do_mult(expect
.f_blocks
);
94 free
= do_mult(expect
.f_bfree
);
95 avail
= do_mult(expect
.f_bavail
);
100 inline std::string
getcwd() {
101 // Assume that path lengths are not greater than this.
102 // This should be fine for testing purposes.
104 char* ret
= ::getcwd(buf
, sizeof(buf
));
105 assert(ret
&& "getcwd failed");
106 return std::string(ret
);
109 inline bool exists(std::string
const& path
) {
111 return ::stat(path
.c_str(), &tmp
) == 0;
113 } // end namespace utils
115 struct scoped_test_env
117 scoped_test_env() : test_root(available_cwd_path()) {
119 // Windows mkdir can create multiple recursive directories
121 std::string cmd
= "mkdir " + test_root
.string();
123 std::string cmd
= "mkdir -p " + test_root
.string();
125 int ret
= std::system(cmd
.c_str());
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
132 test_root
= fs::canonical(test_root
);
137 std::string cmd
= "rmdir /s /q " + test_root
.string();
138 int ret
= std::system(cmd
.c_str());
141 std::string cmd
= "chmod -R 777 " + test_root
.string();
142 int ret
= std::system(cmd
.c_str());
145 cmd
= "rm -r " + test_root
.string();
146 ret
= std::system(cmd
.c_str());
151 scoped_test_env(scoped_test_env
const &) = delete;
152 scoped_test_env
& operator=(scoped_test_env
const &) = delete;
154 fs::path
make_env_path(std::string p
) { return sanitize_path(p
); }
156 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
);
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
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
;
180 auto large_file_fopen
= fopen64
;
181 auto large_file_ftruncate
= ftruncate64
;
182 using large_file_offset_t
= off64_t
;
185 filename
= sanitize_path(std::move(filename
));
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
);
196 #define FOPEN_CLOEXEC_FLAG "e"
198 #define FOPEN_CLOEXEC_FLAG ""
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(),
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
));
219 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
227 std::string
create_file_dir_symlink(fs::path source_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();
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
);
241 std::string
create_symlink(fs::path source_path
,
243 bool sanitize_source
= true) {
244 return create_file_dir_symlink(source_path
, to_path
, sanitize_source
,
248 std::string
create_directory_symlink(fs::path source_path
,
250 bool sanitize_source
= true) {
251 return create_file_dir_symlink(source_path
, to_path
, sanitize_source
,
255 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());
266 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
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)
277 std::string
create_socket(std::string file
) {
278 file
= sanitize_path(std::move(file
));
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
));
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.
298 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();
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
));
311 /// This class generates the following tree:
314 /// ├── bad_symlink -> dne
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
329 class static_test_env
{
330 scoped_test_env 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);
349 const fs::path Root
= env_
.test_root
;
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
;
357 const std::vector
<fs::path
> TestFileList
= {
358 makePath("empty_file"),
359 makePath("non_empty_file"),
360 makePath("dir1/file1"),
361 makePath("dir1/file2")
364 const std::vector
<fs::path
> TestDirList
= {
366 makePath("dir1/dir2"),
367 makePath("dir1/dir2/dir3")
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
382 const std::vector
<fs::path
> DirIterationList
= {
383 makePath("dir1/dir2"),
384 makePath("dir1/file1"),
385 makePath("dir1/file2")
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"),
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")
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"),
421 CWDGuard() : oldCwd_(utils::getcwd()) { }
423 int ret
= ::chdir(oldCwd_
.c_str());
424 assert(ret
== 0 && "chdir failed");
427 CWDGuard(CWDGuard
const&) = delete;
428 CWDGuard
& operator=(CWDGuard
const&) = delete;
433 #if TEST_STD_VER > 17 && defined(__cpp_char8_t)
434 #define CHAR8_ONLY(x) x,
436 #define CHAR8_ONLY(x)
439 #define MKSTR(Str) {Str, TEST_CONCAT(L, Str), CHAR8_ONLY(TEST_CONCAT(u8, Str)) TEST_CONCAT(u, Str), TEST_CONCAT(U, Str)}
441 struct MultiStringType
{
444 #if TEST_STD_VER > 17 && defined(__cpp_char8_t)
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
; }
455 operator const char16_t
* () const { return u16
; }
456 operator const char32_t
* () const { return u32
; }
459 const MultiStringType PathList
[] = {
475 MKSTR("///foo///bar"),
499 MKSTR("c:/foo\\bar"),
501 MKSTR("/finally/we/need/one/really/really/really/really/really/really/really/long/string")
503 const unsigned PathListSize
= sizeof(PathList
) / sizeof(MultiStringType
);
505 template <class Iter
>
506 Iter
IterEnd(Iter B
) {
507 using VT
= typename
std::iterator_traits
<Iter
>::value_type
;
508 for (; *B
!= VT
{}; ++B
)
513 template <class CharT
>
514 const CharT
* StrEnd(CharT
const* P
) {
518 template <class CharT
>
519 std::size_t StrLen(CharT
const* P
) {
520 return StrEnd(P
) - P
;
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.
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
);
531 template <class Iter1
, class Iter2
>
532 bool checkCollectionsEqual(
533 Iter1 start1
, Iter1
const end1
534 , Iter2 start2
, Iter2
const end2
537 while (start1
!= end1
&& start2
!= end2
) {
538 if (*start1
!= *start2
) {
543 return (start1
== end1
&& start2
== end2
);
547 template <class Iter1
, class Iter2
>
548 bool checkCollectionsEqualBackwards(
549 Iter1
const start1
, Iter1 end1
550 , Iter2
const start2
, Iter2 end2
553 while (start1
!= end1
&& start2
!= end2
) {
555 if (*end1
!= *end2
) {
559 return (start1
== end1
&& start2
== end2
);
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) {
567 auto GetErrc
= [&]() {
570 return errc::address_family_not_supported
;
572 return errc::address_not_available
;
574 return errc::address_in_use
;
576 return errc::argument_list_too_long
;
578 assert(false && "Idx out of range");
582 return std::make_error_code(GetErrc());
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
))
595 template <class... ErrcT
>
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
);
601 // Provide our own Sleep routine since std::this_thread::sleep_for is not
602 // available in single-threaded mode.
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
;
608 using Clock
= steady_clock
;
610 const auto wake_time
= Clock::now() + dur
;
611 while (Clock::now() < wake_time
)
615 inline bool PathEq(fs::path
const& LHS
, fs::path
const& RHS
) {
616 return LHS
.native() == RHS
.native();
619 struct ExceptionChecker
{
620 std::errc expected_err
;
621 fs::path expected_path1
;
622 fs::path expected_path2
;
624 const char* func_name
;
625 std::string opt_message
;
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
= {})
633 : expected_err(first_err
), expected_path1(p
), num_paths(1),
634 func_name(fun_name
), opt_message(opt_msg
) {}
636 explicit ExceptionChecker(fs::path p1
, fs::path p2
, std::errc first_err
,
637 const char* fun_name
, std::string opt_msg
= {})
638 : expected_err(first_err
), expected_path1(p1
), expected_path2(p2
),
639 num_paths(2), func_name(fun_name
), opt_message(opt_msg
) {}
641 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
));
648 void check_libcxx_string(fs::filesystem_error
const& Err
) {
649 std::string message
= std::make_error_code(expected_err
).message();
651 std::string additional_msg
= "";
652 if (!opt_message
.empty()) {
653 additional_msg
= opt_message
+ ": ";
655 auto transform_path
= [](const fs::path
& p
) {
656 if (p
.native().empty())
657 return std::string("\"\"");
660 std::string format
= [&]() -> std::string
{
663 return format_string("filesystem error: in %s: %s%s", func_name
,
664 additional_msg
, message
);
666 return format_string("filesystem error: in %s: %s%s [%s]", func_name
,
667 additional_msg
, message
,
668 transform_path(expected_path1
).c_str());
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());
675 TEST_CHECK(false && "unexpected case");
679 TEST_CHECK(format
== Err
.what());
680 if (format
!= Err
.what()) {
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());
688 ExceptionChecker(ExceptionChecker
const&) = delete;
689 ExceptionChecker
& operator=(ExceptionChecker
const&) = delete;
693 #endif /* FILESYSTEM_TEST_HELPER_HPP */