filesystem_test_helper.hpp (17627B)
1 #ifndef FILESYSTEM_TEST_HELPER_HPP 2 #define FILESYSTEM_TEST_HELPER_HPP 3 4 #include "filesystem_include.hpp" 5 6 #include <unistd.h> // for ftruncate 7 8 #include <cassert> 9 #include <cstdio> // for printf 10 #include <string> 11 #include <fstream> 12 #include <random> 13 #include <chrono> 14 #include <vector> 15 #include <regex> 16 17 #include "test_macros.h" 18 #include "rapid-cxx-test.hpp" 19 #include "format_string.hpp" 20 21 // static test helpers 22 23 #ifndef LIBCXX_FILESYSTEM_STATIC_TEST_ROOT 24 #warning "STATIC TESTS DISABLED" 25 #else // LIBCXX_FILESYSTEM_STATIC_TEST_ROOT 26 27 namespace StaticEnv { 28 29 inline fs::path makePath(fs::path const& p) { 30 // env_path is expected not to contain symlinks. 31 static const fs::path env_path = LIBCXX_FILESYSTEM_STATIC_TEST_ROOT; 32 return env_path / p; 33 } 34 35 static const fs::path Root = LIBCXX_FILESYSTEM_STATIC_TEST_ROOT; 36 37 static const fs::path TestFileList[] = { 38 makePath("empty_file"), 39 makePath("non_empty_file"), 40 makePath("dir1/file1"), 41 makePath("dir1/file2") 42 }; 43 const std::size_t TestFileListSize = sizeof(TestFileList) / sizeof(fs::path); 44 45 static const fs::path TestDirList[] = { 46 makePath("dir1"), 47 makePath("dir1/dir2"), 48 makePath("dir1/dir2/dir3") 49 }; 50 const std::size_t TestDirListSize = sizeof(TestDirList) / sizeof(fs::path); 51 52 static const fs::path File = TestFileList[0]; 53 static const fs::path Dir = TestDirList[0]; 54 static const fs::path Dir2 = TestDirList[1]; 55 static const fs::path Dir3 = TestDirList[2]; 56 static const fs::path SymlinkToFile = makePath("symlink_to_empty_file"); 57 static const fs::path SymlinkToDir = makePath("symlink_to_dir"); 58 static const fs::path BadSymlink = makePath("bad_symlink"); 59 static const fs::path DNE = makePath("DNE"); 60 static const fs::path EmptyFile = TestFileList[0]; 61 static const fs::path NonEmptyFile = TestFileList[1]; 62 static const fs::path CharFile = "/dev/null"; // Hopefully this exists 63 64 static const fs::path DirIterationList[] = { 65 makePath("dir1/dir2"), 66 makePath("dir1/file1"), 67 makePath("dir1/file2") 68 }; 69 const std::size_t DirIterationListSize = sizeof(DirIterationList) 70 / sizeof(fs::path); 71 72 static const fs::path DirIterationListDepth1[] = { 73 makePath("dir1/dir2/afile3"), 74 makePath("dir1/dir2/dir3"), 75 makePath("dir1/dir2/symlink_to_dir3"), 76 makePath("dir1/dir2/file4"), 77 }; 78 79 static const fs::path RecDirIterationList[] = { 80 makePath("dir1/dir2"), 81 makePath("dir1/file1"), 82 makePath("dir1/file2"), 83 makePath("dir1/dir2/afile3"), 84 makePath("dir1/dir2/dir3"), 85 makePath("dir1/dir2/symlink_to_dir3"), 86 makePath("dir1/dir2/file4"), 87 makePath("dir1/dir2/dir3/file5") 88 }; 89 90 static const fs::path RecDirFollowSymlinksIterationList[] = { 91 makePath("dir1/dir2"), 92 makePath("dir1/file1"), 93 makePath("dir1/file2"), 94 makePath("dir1/dir2/afile3"), 95 makePath("dir1/dir2/dir3"), 96 makePath("dir1/dir2/file4"), 97 makePath("dir1/dir2/dir3/file5"), 98 makePath("dir1/dir2/symlink_to_dir3"), 99 makePath("dir1/dir2/symlink_to_dir3/file5"), 100 }; 101 102 } // namespace StaticEnv 103 104 #endif // LIBCXX_FILESYSTEM_STATIC_TEST_ROOT 105 106 #ifndef LIBCXX_FILESYSTEM_DYNAMIC_TEST_ROOT 107 #warning LIBCXX_FILESYSTEM_DYNAMIC_TEST_ROOT must be defined 108 #else // LIBCXX_FILESYSTEM_DYNAMIC_TEST_ROOT 109 110 #ifndef LIBCXX_FILESYSTEM_DYNAMIC_TEST_HELPER 111 #error LIBCXX_FILESYSTEM_DYNAMIC_TEST_HELPER must be defined 112 #endif 113 114 namespace random_utils { 115 inline char to_hex(int ch) { 116 return ch < 10 ? static_cast<char>('0' + ch) 117 : static_cast<char>('a' + (ch - 10)); 118 } 119 120 inline char random_hex_char() { 121 static std::mt19937 rd{std::random_device{}()}; 122 static std::uniform_int_distribution<int> mrand{0, 15}; 123 return to_hex(mrand(rd)); 124 } 125 126 } // namespace random_utils 127 128 struct scoped_test_env 129 { 130 scoped_test_env() : test_root(random_env_path()) 131 { fs_helper_run(fs_make_cmd("init_test_directory", test_root)); } 132 133 ~scoped_test_env() 134 { fs_helper_run(fs_make_cmd("destroy_test_directory", test_root)); } 135 136 scoped_test_env(scoped_test_env const &) = delete; 137 scoped_test_env & operator=(scoped_test_env const &) = delete; 138 139 fs::path make_env_path(std::string p) { return sanitize_path(p); } 140 141 std::string sanitize_path(std::string raw) { 142 assert(raw.find("..") == std::string::npos); 143 std::string const& root = test_root.native(); 144 if (root.compare(0, root.size(), raw, 0, root.size()) != 0) { 145 assert(raw.front() != '\\'); 146 fs::path tmp(test_root); 147 tmp /= raw; 148 return std::move(const_cast<std::string&>(tmp.native())); 149 } 150 return raw; 151 } 152 153 // Purposefully using a size potentially larger than off_t here so we can 154 // test the behavior of libc++fs when it is built with _FILE_OFFSET_BITS=64 155 // but the caller is not (std::filesystem also uses uintmax_t rather than 156 // off_t). On a 32-bit system this allows us to create a file larger than 157 // 2GB. 158 std::string create_file(std::string filename, uintmax_t size = 0) { 159 #if defined(__LP64__) 160 auto large_file_fopen = fopen; 161 auto large_file_ftruncate = ftruncate; 162 using large_file_offset_t = off_t; 163 #else 164 auto large_file_fopen = fopen64; 165 auto large_file_ftruncate = ftruncate64; 166 using large_file_offset_t = off64_t; 167 #endif 168 169 filename = sanitize_path(std::move(filename)); 170 171 if (size > std::numeric_limits<large_file_offset_t>::max()) { 172 fprintf(stderr, "create_file(%s, %ju) too large\n", 173 filename.c_str(), size); 174 abort(); 175 } 176 177 FILE* file = large_file_fopen(filename.c_str(), "we"); 178 if (file == nullptr) { 179 fprintf(stderr, "fopen %s failed: %s\n", filename.c_str(), 180 strerror(errno)); 181 abort(); 182 } 183 184 if (large_file_ftruncate( 185 fileno(file), static_cast<large_file_offset_t>(size)) == -1) { 186 fprintf(stderr, "ftruncate %s %ju failed: %s\n", filename.c_str(), 187 size, strerror(errno)); 188 fclose(file); 189 abort(); 190 } 191 192 fclose(file); 193 return filename; 194 } 195 196 std::string create_dir(std::string filename) { 197 filename = sanitize_path(std::move(filename)); 198 fs_helper_run(fs_make_cmd("create_dir", filename)); 199 return filename; 200 } 201 202 std::string create_symlink(std::string source, std::string to) { 203 source = sanitize_path(std::move(source)); 204 to = sanitize_path(std::move(to)); 205 fs_helper_run(fs_make_cmd("create_symlink", source, to)); 206 return to; 207 } 208 209 std::string create_hardlink(std::string source, std::string to) { 210 source = sanitize_path(std::move(source)); 211 to = sanitize_path(std::move(to)); 212 fs_helper_run(fs_make_cmd("create_hardlink", source, to)); 213 return to; 214 } 215 216 std::string create_fifo(std::string file) { 217 file = sanitize_path(std::move(file)); 218 fs_helper_run(fs_make_cmd("create_fifo", file)); 219 return file; 220 } 221 222 // OS X and FreeBSD doesn't support socket files so we shouldn't even 223 // allow tests to call this unguarded. 224 #if !defined(__FreeBSD__) && !defined(__APPLE__) 225 std::string create_socket(std::string file) { 226 file = sanitize_path(std::move(file)); 227 fs_helper_run(fs_make_cmd("create_socket", file)); 228 return file; 229 } 230 #endif 231 232 fs::path const test_root; 233 234 private: 235 static std::string unique_path_suffix() { 236 std::string model = "test.%%%%%%"; 237 for (auto & ch : model) { 238 if (ch == '%') 239 ch = random_utils::random_hex_char(); 240 } 241 return model; 242 } 243 244 // This could potentially introduce a filesystem race with other tests 245 // running at the same time, but oh well, it's just test code. 246 static inline fs::path random_env_path() { 247 static const char* env_path = LIBCXX_FILESYSTEM_DYNAMIC_TEST_ROOT; 248 fs::path p = fs::path(env_path) / unique_path_suffix(); 249 assert(p.parent_path() == env_path); 250 return p; 251 } 252 253 static inline std::string make_arg(std::string const& arg) { 254 return "'" + arg + "'"; 255 } 256 257 static inline std::string make_arg(std::size_t arg) { 258 return std::to_string(arg); 259 } 260 261 template <class T> 262 static inline std::string 263 fs_make_cmd(std::string const& cmd_name, T const& arg) { 264 return cmd_name + "(" + make_arg(arg) + ")"; 265 } 266 267 template <class T, class U> 268 static inline std::string 269 fs_make_cmd(std::string const& cmd_name, T const& arg1, U const& arg2) { 270 return cmd_name + "(" + make_arg(arg1) + ", " + make_arg(arg2) + ")"; 271 } 272 273 static inline void fs_helper_run(std::string const& raw_cmd) { 274 // check that the fs test root in the environment matches what we were 275 // compiled with. 276 static bool checked = checkDynamicTestRoot(); 277 ((void)checked); 278 std::string cmd = LIBCXX_FILESYSTEM_DYNAMIC_TEST_HELPER; 279 cmd += " \"" + raw_cmd + "\""; 280 int ret = std::system(cmd.c_str()); 281 assert(ret == 0); 282 } 283 284 static bool checkDynamicTestRoot() { 285 // LIBCXX_FILESYSTEM_DYNAMIC_TEST_ROOT is expected not to contain symlinks. 286 char* fs_root = std::getenv("LIBCXX_FILESYSTEM_DYNAMIC_TEST_ROOT"); 287 if (!fs_root) { 288 std::printf("ERROR: LIBCXX_FILESYSTEM_DYNAMIC_TEST_ROOT must be a defined " 289 "environment variable when running the test.\n"); 290 std::abort(); 291 } 292 if (std::string(fs_root) != LIBCXX_FILESYSTEM_DYNAMIC_TEST_ROOT) { 293 std::printf("ERROR: LIBCXX_FILESYSTEM_DYNAMIC_TEST_ROOT environment variable" 294 " must have the same value as when the test was compiled.\n"); 295 std::printf(" Current Value: '%s'\n", fs_root); 296 std::printf(" Expected Value: '%s'\n", LIBCXX_FILESYSTEM_DYNAMIC_TEST_ROOT); 297 std::abort(); 298 } 299 return true; 300 } 301 302 }; 303 304 #endif // LIBCXX_FILESYSTEM_DYNAMIC_TEST_ROOT 305 306 // Misc test types 307 308 #define CONCAT2(LHS, RHS) LHS##RHS 309 #define CONCAT(LHS, RHS) CONCAT2(LHS, RHS) 310 #define MKSTR(Str) {Str, CONCAT(L, Str), CONCAT(u, Str), CONCAT(U, Str)} 311 312 struct MultiStringType { 313 const char* s; 314 const wchar_t* w; 315 const char16_t* u16; 316 const char32_t* u32; 317 318 operator const char* () const { return s; } 319 operator const wchar_t* () const { return w; } 320 operator const char16_t* () const { return u16; } 321 operator const char32_t* () const { return u32; } 322 }; 323 324 const MultiStringType PathList[] = { 325 MKSTR(""), 326 MKSTR(" "), 327 MKSTR("//"), 328 MKSTR("."), 329 MKSTR(".."), 330 MKSTR("foo"), 331 MKSTR("/"), 332 MKSTR("/foo"), 333 MKSTR("foo/"), 334 MKSTR("/foo/"), 335 MKSTR("foo/bar"), 336 MKSTR("/foo/bar"), 337 MKSTR("//net"), 338 MKSTR("//net/foo"), 339 MKSTR("///foo///"), 340 MKSTR("///foo///bar"), 341 MKSTR("/."), 342 MKSTR("./"), 343 MKSTR("/.."), 344 MKSTR("../"), 345 MKSTR("foo/."), 346 MKSTR("foo/.."), 347 MKSTR("foo/./"), 348 MKSTR("foo/./bar"), 349 MKSTR("foo/../"), 350 MKSTR("foo/../bar"), 351 MKSTR("c:"), 352 MKSTR("c:/"), 353 MKSTR("c:foo"), 354 MKSTR("c:/foo"), 355 MKSTR("c:foo/"), 356 MKSTR("c:/foo/"), 357 MKSTR("c:/foo/bar"), 358 MKSTR("prn:"), 359 MKSTR("c:\\"), 360 MKSTR("c:\\foo"), 361 MKSTR("c:foo\\"), 362 MKSTR("c:\\foo\\"), 363 MKSTR("c:\\foo/"), 364 MKSTR("c:/foo\\bar"), 365 MKSTR("//"), 366 MKSTR("/finally/we/need/one/really/really/really/really/really/really/really/long/string") 367 }; 368 const unsigned PathListSize = sizeof(PathList) / sizeof(MultiStringType); 369 370 template <class Iter> 371 Iter IterEnd(Iter B) { 372 using VT = typename std::iterator_traits<Iter>::value_type; 373 for (; *B != VT{}; ++B) 374 ; 375 return B; 376 } 377 378 template <class CharT> 379 const CharT* StrEnd(CharT const* P) { 380 return IterEnd(P); 381 } 382 383 template <class CharT> 384 std::size_t StrLen(CharT const* P) { 385 return StrEnd(P) - P; 386 } 387 388 // Testing the allocation behavior of the code_cvt functions requires 389 // *knowing* that the allocation was not done by "path::__str_". 390 // This hack forces path to allocate enough memory. 391 inline void PathReserve(fs::path& p, std::size_t N) { 392 auto const& native_ref = p.native(); 393 const_cast<std::string&>(native_ref).reserve(N); 394 } 395 396 template <class Iter1, class Iter2> 397 bool checkCollectionsEqual( 398 Iter1 start1, Iter1 const end1 399 , Iter2 start2, Iter2 const end2 400 ) 401 { 402 while (start1 != end1 && start2 != end2) { 403 if (*start1 != *start2) { 404 return false; 405 } 406 ++start1; ++start2; 407 } 408 return (start1 == end1 && start2 == end2); 409 } 410 411 412 template <class Iter1, class Iter2> 413 bool checkCollectionsEqualBackwards( 414 Iter1 const start1, Iter1 end1 415 , Iter2 const start2, Iter2 end2 416 ) 417 { 418 while (start1 != end1 && start2 != end2) { 419 --end1; --end2; 420 if (*end1 != *end2) { 421 return false; 422 } 423 } 424 return (start1 == end1 && start2 == end2); 425 } 426 427 // We often need to test that the error_code was cleared if no error occurs 428 // this function returns an error_code which is set to an error that will 429 // never be returned by the filesystem functions. 430 inline std::error_code GetTestEC(unsigned Idx = 0) { 431 using std::errc; 432 auto GetErrc = [&]() { 433 switch (Idx) { 434 case 0: 435 return errc::address_family_not_supported; 436 case 1: 437 return errc::address_not_available; 438 case 2: 439 return errc::address_in_use; 440 case 3: 441 return errc::argument_list_too_long; 442 default: 443 assert(false && "Idx out of range"); 444 std::abort(); 445 } 446 }; 447 return std::make_error_code(GetErrc()); 448 } 449 450 inline bool ErrorIsImp(const std::error_code& ec, 451 std::vector<std::errc> const& errors) { 452 for (auto errc : errors) { 453 if (ec == std::make_error_code(errc)) 454 return true; 455 } 456 return false; 457 } 458 459 template <class... ErrcT> 460 inline bool ErrorIs(const std::error_code& ec, std::errc First, ErrcT... Rest) { 461 std::vector<std::errc> errors = {First, Rest...}; 462 return ErrorIsImp(ec, errors); 463 } 464 465 // Provide our own Sleep routine since std::this_thread::sleep_for is not 466 // available in single-threaded mode. 467 void SleepFor(std::chrono::seconds dur) { 468 using namespace std::chrono; 469 #if defined(_LIBCPP_HAS_NO_MONOTONIC_CLOCK) 470 using Clock = system_clock; 471 #else 472 using Clock = steady_clock; 473 #endif 474 const auto wake_time = Clock::now() + dur; 475 while (Clock::now() < wake_time) 476 ; 477 } 478 479 inline bool PathEq(fs::path const& LHS, fs::path const& RHS) { 480 return LHS.native() == RHS.native(); 481 } 482 483 struct ExceptionChecker { 484 std::errc expected_err; 485 fs::path expected_path1; 486 fs::path expected_path2; 487 unsigned num_paths; 488 const char* func_name; 489 std::string opt_message; 490 491 explicit ExceptionChecker(std::errc first_err, const char* func_name, 492 std::string opt_msg = {}) 493 : expected_err{first_err}, num_paths(0), func_name(func_name), 494 opt_message(opt_msg) {} 495 explicit ExceptionChecker(fs::path p, std::errc first_err, 496 const char* func_name, std::string opt_msg = {}) 497 : expected_err(first_err), expected_path1(p), num_paths(1), 498 func_name(func_name), opt_message(opt_msg) {} 499 500 explicit ExceptionChecker(fs::path p1, fs::path p2, std::errc first_err, 501 const char* func_name, std::string opt_msg = {}) 502 : expected_err(first_err), expected_path1(p1), expected_path2(p2), 503 num_paths(2), func_name(func_name), opt_message(opt_msg) {} 504 505 void operator()(fs::filesystem_error const& Err) { 506 TEST_CHECK(ErrorIsImp(Err.code(), {expected_err})); 507 TEST_CHECK(Err.path1() == expected_path1); 508 TEST_CHECK(Err.path2() == expected_path2); 509 LIBCPP_ONLY(check_libcxx_string(Err)); 510 } 511 512 void check_libcxx_string(fs::filesystem_error const& Err) { 513 std::string message = std::make_error_code(expected_err).message(); 514 515 std::string additional_msg = ""; 516 if (!opt_message.empty()) { 517 additional_msg = opt_message + ": "; 518 } 519 auto transform_path = [](const fs::path& p) { 520 if (p.native().empty()) 521 return "\"\""; 522 return p.c_str(); 523 }; 524 std::string format = [&]() -> std::string { 525 switch (num_paths) { 526 case 0: 527 return format_string("filesystem error: in %s: %s%s", func_name, 528 additional_msg, message); 529 case 1: 530 return format_string("filesystem error: in %s: %s%s [%s]", func_name, 531 additional_msg, message, 532 transform_path(expected_path1)); 533 case 2: 534 return format_string("filesystem error: in %s: %s%s [%s] [%s]", 535 func_name, additional_msg, message, 536 transform_path(expected_path1), 537 transform_path(expected_path2)); 538 default: 539 TEST_CHECK(false && "unexpected case"); 540 return ""; 541 } 542 }(); 543 TEST_CHECK(format == Err.what()); 544 if (format != Err.what()) { 545 fprintf(stderr, 546 "filesystem_error::what() does not match expected output:\n"); 547 fprintf(stderr, " expected: \"%s\"\n", format.c_str()); 548 fprintf(stderr, " actual: \"%s\"\n\n", Err.what()); 549 } 550 } 551 552 ExceptionChecker(ExceptionChecker const&) = delete; 553 ExceptionChecker& operator=(ExceptionChecker const&) = delete; 554 555 }; 556 557 #endif /* FILESYSTEM_TEST_HELPER_HPP */