#include #include #include #include #include #include #include using vcpkg::Test::AllowSymlinks; using vcpkg::Test::base_temporary_directory; using vcpkg::Test::can_create_symlinks; #define CHECK_EC_ON_FILE(file, ec) \ do \ { \ if (ec) \ { \ FAIL(file << ": " << ec.message()); \ } \ } while (0) namespace { using uid_t = std::uniform_int_distribution; using urbg_t = std::mt19937_64; urbg_t get_urbg(std::uint64_t index) { // smallest prime > 2**63 - 1 return urbg_t{index + 9223372036854775837ULL}; } std::string get_random_filename(urbg_t& urbg) { return vcpkg::Strings::b32_encode(uid_t{}(urbg)); } struct MaxDepth { std::uint64_t i; explicit MaxDepth(std::uint64_t i) : i(i) { } operator uint64_t() const { return i; } }; struct Width { std::uint64_t i; explicit Width(std::uint64_t i) : i(i) { } operator uint64_t() const { return i; } }; struct CurrentDepth { std::uint64_t i; explicit CurrentDepth(std::uint64_t i) : i(i) { } operator uint64_t() const { return i; } CurrentDepth incremented() const { return CurrentDepth{i + 1}; } }; void create_directory_tree(urbg_t& urbg, vcpkg::Files::Filesystem& fs, const fs::path& base, MaxDepth max_depth, AllowSymlinks allow_symlinks = AllowSymlinks::Yes, Width width = Width{5}, CurrentDepth current_depth = CurrentDepth{0}) { // we want ~70% of our "files" to be directories, and then a third // each of the remaining ~30% to be regular files, directory symlinks, // and regular symlinks constexpr std::uint64_t directory_min_tag = 0; constexpr std::uint64_t directory_max_tag = 6; constexpr std::uint64_t regular_file_tag = 7; constexpr std::uint64_t regular_symlink_tag = 8; constexpr std::uint64_t directory_symlink_tag = 9; allow_symlinks = AllowSymlinks{allow_symlinks && can_create_symlinks()}; // if we're at the max depth, we only want to build non-directories std::uint64_t file_type; if (current_depth >= max_depth) { file_type = uid_t{regular_file_tag, directory_symlink_tag}(urbg); } else if (current_depth < 2) { file_type = directory_min_tag; } else { file_type = uid_t{directory_min_tag, regular_symlink_tag}(urbg); } if (!allow_symlinks && file_type > regular_file_tag) { file_type = regular_file_tag; } std::error_code ec; if (file_type <= directory_max_tag) { fs.create_directory(base, ec); if (ec) { CHECK_EC_ON_FILE(base, ec); } for (std::uint64_t i = 0; i < width; ++i) { create_directory_tree(urbg, fs, base / get_random_filename(urbg), max_depth, allow_symlinks, width, current_depth.incremented()); } } else if (file_type == regular_file_tag) { // regular file fs.write_contents(base, "", ec); } else if (file_type == regular_symlink_tag) { // regular symlink auto base_link = base; base_link.replace_filename(fs::u8string(base.filename()) + "-orig"); fs.write_contents(base_link, "", ec); CHECK_EC_ON_FILE(base_link, ec); vcpkg::Test::create_symlink(base_link, base, ec); } else // file_type == directory_symlink_tag { // directory symlink auto parent = base; parent.remove_filename(); vcpkg::Test::create_directory_symlink(parent, base, ec); } CHECK_EC_ON_FILE(base, ec); REQUIRE(fs::exists(fs.symlink_status(base, ec))); CHECK_EC_ON_FILE(base, ec); } vcpkg::Files::Filesystem& setup() { auto& fs = vcpkg::Files::get_real_filesystem(); std::error_code ec; fs.create_directory(base_temporary_directory(), ec); CHECK_EC_ON_FILE(base_temporary_directory(), ec); return fs; } } TEST_CASE ("fs::combine works correctly", "[filesystem][files]") { using namespace fs; using namespace vcpkg::Files; CHECK(combine(u8path("/a/b"), u8path("c/d")) == u8path("/a/b/c/d")); CHECK(combine(u8path("a/b"), u8path("c/d")) == u8path("a/b/c/d")); CHECK(combine(u8path("/a/b"), u8path("/c/d")) == u8path("/c/d")); #if defined(_WIN32) CHECK(combine(u8path("C:/a/b"), u8path("c/d")) == u8path("C:/a/b/c/d")); CHECK(combine(u8path("C:a/b"), u8path("c/d")) == u8path("C:a/b/c/d")); CHECK(combine(u8path("C:a/b"), u8path("/c/d")) == u8path("C:/c/d")); CHECK(combine(u8path("C:/a/b"), u8path("/c/d")) == u8path("C:/c/d")); CHECK(combine(u8path("C:/a/b"), u8path("D:/c/d")) == u8path("D:/c/d")); CHECK(combine(u8path("C:/a/b"), u8path("D:c/d")) == u8path("D:c/d")); CHECK(combine(u8path("C:/a/b"), u8path("C:c/d")) == u8path("C:/a/b/c/d")); #endif } TEST_CASE ("remove all", "[files]") { auto urbg = get_urbg(0); auto& fs = setup(); fs::path temp_dir = base_temporary_directory() / get_random_filename(urbg); INFO("temp dir is: " << temp_dir); create_directory_tree(urbg, fs, temp_dir, MaxDepth{5}); std::error_code ec; fs::path fp; fs.remove_all(temp_dir, ec, fp); CHECK_EC_ON_FILE(fp, ec); REQUIRE_FALSE(fs.exists(temp_dir, ec)); CHECK_EC_ON_FILE(temp_dir, ec); } #if defined(_WIN32) TEST_CASE ("win32_fix_path_case", "[files]") { using vcpkg::Files::win32_fix_path_case; // This test assumes that the Windows directory is C:\Windows CHECK(win32_fix_path_case(L"") == L""); CHECK(win32_fix_path_case(L"C:") == L"C:"); CHECK(win32_fix_path_case(L"c:") == L"C:"); CHECK(win32_fix_path_case(L"C:/") == L"C:\\"); CHECK(win32_fix_path_case(L"C:\\") == L"C:\\"); CHECK(win32_fix_path_case(L"c:\\") == L"C:\\"); CHECK(win32_fix_path_case(L"C:\\WiNdOws") == L"C:\\Windows"); CHECK(win32_fix_path_case(L"c:\\WiNdOws\\") == L"C:\\Windows\\"); CHECK(win32_fix_path_case(L"C://///////WiNdOws") == L"C:\\Windows"); CHECK(win32_fix_path_case(L"c:\\/\\/WiNdOws\\/") == L"C:\\Windows\\"); auto& fs = vcpkg::Files::get_real_filesystem(); auto original_cwd = fs.current_path(VCPKG_LINE_INFO); fs.current_path(L"C:\\", VCPKG_LINE_INFO); CHECK(win32_fix_path_case(L"\\") == L"\\"); CHECK(win32_fix_path_case(L"\\/\\WiNdOws") == L"\\Windows"); CHECK(win32_fix_path_case(L"\\WiNdOws") == L"\\Windows"); CHECK(win32_fix_path_case(L"\\WiNdOws") == L"\\Windows"); CHECK(win32_fix_path_case(L"c:WiNdOws") == L"C:Windows"); CHECK(win32_fix_path_case(L"c:WiNdOws/system32") == L"C:Windows\\System32"); fs.current_path(original_cwd, VCPKG_LINE_INFO); fs.create_directories("SuB/Dir/Ectory", VCPKG_LINE_INFO); CHECK(win32_fix_path_case(L"sub") == L"SuB"); CHECK(win32_fix_path_case(L"SUB") == L"SuB"); CHECK(win32_fix_path_case(L"sub/") == L"SuB\\"); CHECK(win32_fix_path_case(L"sub/dir") == L"SuB\\Dir"); CHECK(win32_fix_path_case(L"sub/dir/") == L"SuB\\Dir\\"); CHECK(win32_fix_path_case(L"sub/dir/ectory") == L"SuB\\Dir\\Ectory"); CHECK(win32_fix_path_case(L"sub/dir/ectory/") == L"SuB\\Dir\\Ectory\\"); fs.remove_all("SuB", VCPKG_LINE_INFO); CHECK(win32_fix_path_case(L"//nonexistent_server\\nonexistent_share\\") == L"\\\\nonexistent_server\\nonexistent_share\\"); CHECK(win32_fix_path_case(L"\\\\nonexistent_server\\nonexistent_share\\") == L"\\\\nonexistent_server\\nonexistent_share\\"); CHECK(win32_fix_path_case(L"\\\\nonexistent_server\\nonexistent_share") == L"\\\\nonexistent_server\\nonexistent_share"); CHECK(win32_fix_path_case(L"///three_slashes_not_a_server\\subdir\\") == L"\\three_slashes_not_a_server\\subdir\\"); CHECK(win32_fix_path_case(L"\\??\\c:\\WiNdOws") == L"\\??\\c:\\WiNdOws"); CHECK(win32_fix_path_case(L"\\\\?\\c:\\WiNdOws") == L"\\\\?\\c:\\WiNdOws"); CHECK(win32_fix_path_case(L"\\\\.\\c:\\WiNdOws") == L"\\\\.\\c:\\WiNdOws"); CHECK(win32_fix_path_case(L"c:\\/\\/Nonexistent\\/path/here") == L"C:\\Nonexistent\\path\\here"); } #endif // _WIN32 TEST_CASE ("add_filename", "[files]") { using vcpkg::Files::add_filename; using vcpkg::Files::preferred_separator; CHECK(add_filename("a/b", "c") == std::string("a/b") + preferred_separator + "c"); CHECK(add_filename("a/b/", "c") == "a/b/c"); CHECK(add_filename("a/b\\", "c") == "a/b\\c"); CHECK(add_filename("", "c") == "c"); // note that we don't special case slashes in the second argument; the caller shouldn't do that CHECK(add_filename("a/b/", "\\c") == "a/b/\\c"); CHECK(add_filename("a/b\\", "/c") == "a/b\\/c"); } #if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) TEST_CASE ("remove all -- benchmarks", "[files][!benchmark]") { auto urbg = get_urbg(1); auto& fs = setup(); struct { urbg_t& urbg; vcpkg::Files::Filesystem& fs; void operator()(Catch::Benchmark::Chronometer& meter, MaxDepth max_depth, AllowSymlinks allow_symlinks) const { std::vector temp_dirs; temp_dirs.resize(meter.runs()); std::generate(begin(temp_dirs), end(temp_dirs), [&] { fs::path temp_dir = base_temporary_directory() / get_random_filename(urbg); create_directory_tree(urbg, fs, temp_dir, max_depth, allow_symlinks); return temp_dir; }); meter.measure([&](int run) { std::error_code ec; fs::path fp; const auto& temp_dir = temp_dirs[run]; fs.remove_all(temp_dir, ec, fp); CHECK_EC_ON_FILE(fp, ec); }); for (const auto& dir : temp_dirs) { std::error_code ec; REQUIRE_FALSE(fs.exists(dir, ec)); CHECK_EC_ON_FILE(dir, ec); } } } do_benchmark = {urbg, fs}; BENCHMARK_ADVANCED("small directory, no symlinks")(Catch::Benchmark::Chronometer meter) { do_benchmark(meter, MaxDepth{2}, AllowSymlinks::No); }; BENCHMARK_ADVANCED("large directory, no symlinks")(Catch::Benchmark::Chronometer meter) { do_benchmark(meter, MaxDepth{5}, AllowSymlinks::No); }; if (can_create_symlinks()) { BENCHMARK_ADVANCED("small directory, symlinks")(Catch::Benchmark::Chronometer meter) { do_benchmark(meter, MaxDepth{2}, AllowSymlinks::Yes); }; BENCHMARK_ADVANCED("large directory, symlinks")(Catch::Benchmark::Chronometer meter) { do_benchmark(meter, MaxDepth{5}, AllowSymlinks::Yes); }; } } #endif