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