vcpkg/toolsrc/src/vcpkg-test/files.cpp

244 lines
7.8 KiB
C++

#include <catch2/catch.hpp>
#include <vcpkg-test/util.h>
#include <vcpkg/base/files.h>
#include <vcpkg/base/strings.h>
#include <iostream>
#include <random>
#include <vector>
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<std::uint64_t>;
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})
{
std::random_device rd;
// 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(base.filename().u8string() + "-orig");
fs.write_contents(base_link, "", ec);
CHECK_EC_ON_FILE(base_link, ec);
vcpkg::Test::create_symlink(base_link, base, ec);
}
else // 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 ("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(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<fs::path> 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