mirror of
https://github.com/mhx/dwarfs.git
synced 2025-09-09 12:28:13 -04:00
refactor: factor out helpers to simplify/split tool_main_test.cpp
This commit is contained in:
parent
a8d8e0ad84
commit
b64d186a26
@ -465,7 +465,7 @@ endif()
|
||||
add_custom_target(symlinks ALL DEPENDS ${SYMLINKS})
|
||||
|
||||
if(WITH_TESTS OR WITH_BENCHMARKS OR WITH_FUZZ)
|
||||
add_library(dwarfs_test_helpers OBJECT
|
||||
add_library(dwarfs_test_helpers
|
||||
test/test_helpers.cpp
|
||||
test/test_iolayer.cpp
|
||||
test/loremipsum.cpp
|
||||
@ -477,6 +477,16 @@ if(WITH_TESTS OR WITH_BENCHMARKS OR WITH_FUZZ)
|
||||
endif()
|
||||
target_link_libraries(dwarfs_test_helpers PUBLIC dwarfs_reader dwarfs_writer dwarfs_tool)
|
||||
set_property(TARGET dwarfs_test_helpers PROPERTY CXX_STANDARD ${DWARFS_CXX_STANDARD})
|
||||
target_compile_definitions(dwarfs_test_helpers
|
||||
PUBLIC TEST_DATA_DIR=\"${CMAKE_SOURCE_DIR}/test\"
|
||||
TOOLS_BIN_DIR=\"${CMAKE_CURRENT_BINARY_DIR}\")
|
||||
endif()
|
||||
|
||||
if(WITH_TESTS AND WITH_TOOLS)
|
||||
add_library(dwarfs_tool_main_tester test/test_tool_main_tester.cpp)
|
||||
target_link_libraries(dwarfs_tool_main_tester PUBLIC dwarfs_test_helpers GTest::gtest)
|
||||
target_link_libraries(dwarfs_tool_main_tester PRIVATE mkdwarfs_main dwarfsck_main dwarfsextract_main)
|
||||
set_property(TARGET dwarfs_tool_main_tester PROPERTY CXX_STANDARD ${DWARFS_CXX_STANDARD})
|
||||
endif()
|
||||
|
||||
if(WITH_TESTS)
|
||||
@ -572,7 +582,7 @@ if(WITH_TESTS)
|
||||
endif()
|
||||
|
||||
add_library(dwarfs_test_main OBJECT test/test_main.cpp)
|
||||
target_link_libraries(dwarfs_test_main PUBLIC gtest gmock)
|
||||
target_link_libraries(dwarfs_test_main PUBLIC GTest::gtest GTest::gmock)
|
||||
|
||||
foreach (test ${DWARFS_TESTS})
|
||||
if(NOT TARGET ${test})
|
||||
@ -598,10 +608,6 @@ if(WITH_TESTS)
|
||||
target_include_directories(${test} SYSTEM BEFORE PRIVATE ${gmock_include_dirs} ${gtest_include_dirs})
|
||||
endif()
|
||||
|
||||
target_compile_definitions(${test}
|
||||
PRIVATE TEST_DATA_DIR=\"${CMAKE_SOURCE_DIR}/test\"
|
||||
TOOLS_BIN_DIR=\"${CMAKE_CURRENT_BINARY_DIR}\")
|
||||
|
||||
if(WIN32)
|
||||
target_compile_options(${test} PRIVATE /bigobj)
|
||||
endif()
|
||||
@ -610,7 +616,7 @@ if(WITH_TESTS)
|
||||
endforeach()
|
||||
|
||||
if(TARGET tool_main_test)
|
||||
target_link_libraries(tool_main_test PRIVATE mkdwarfs_main dwarfsck_main dwarfsextract_main PkgConfig::LIBARCHIVE)
|
||||
target_link_libraries(tool_main_test PRIVATE dwarfs_tool_main_tester PkgConfig::LIBARCHIVE)
|
||||
endif()
|
||||
|
||||
if(TARGET dwarfs_unit_tests)
|
||||
|
368
test/test_tool_main_tester.cpp
Normal file
368
test/test_tool_main_tester.cpp
Normal file
@ -0,0 +1,368 @@
|
||||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/**
|
||||
* \author Marcus Holland-Moritz (github@mhxnet.de)
|
||||
* \copyright Copyright (c) Marcus Holland-Moritz
|
||||
*
|
||||
* This file is part of dwarfs.
|
||||
*
|
||||
* dwarfs is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* dwarfs is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with dwarfs. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
#include <dwarfs/util.h>
|
||||
|
||||
#include <dwarfs_tool_main.h>
|
||||
|
||||
#include "test_tool_main_tester.h"
|
||||
|
||||
namespace dwarfs::test {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace {
|
||||
|
||||
struct locale_setup_helper {
|
||||
locale_setup_helper() { setup_default_locale(); }
|
||||
};
|
||||
|
||||
inline void setup_locale() { static locale_setup_helper helper; }
|
||||
|
||||
} // namespace
|
||||
|
||||
fs::path const test_dir = fs::path(TEST_DATA_DIR).make_preferred();
|
||||
fs::path const audio_data_dir = test_dir / "pcmaudio";
|
||||
fs::path const fits_data_dir = test_dir / "fits";
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, input_mode m) {
|
||||
switch (m) {
|
||||
case input_mode::from_file:
|
||||
os << "from_file";
|
||||
break;
|
||||
case input_mode::from_stdin:
|
||||
os << "from_stdin";
|
||||
break;
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, path_type m) {
|
||||
switch (m) {
|
||||
case path_type::relative:
|
||||
os << "relative";
|
||||
break;
|
||||
case path_type::absolute:
|
||||
os << "absolute";
|
||||
break;
|
||||
case path_type::mixed:
|
||||
os << "mixed";
|
||||
break;
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
void tool_main_test::SetUp() {
|
||||
setup_locale();
|
||||
iol = std::make_unique<test::test_iolayer>();
|
||||
}
|
||||
|
||||
void tool_main_test::TearDown() { iol.reset(); }
|
||||
|
||||
tester_common::tester_common(main_ptr_t mp, std::string toolname,
|
||||
std::shared_ptr<test::os_access_mock> pos)
|
||||
: fa{std::make_shared<test::test_file_access>()}
|
||||
, os{std::move(pos)}
|
||||
, iol{std::make_unique<test::test_iolayer>(os, fa)}
|
||||
, main_{mp}
|
||||
, toolname_{std::move(toolname)} {
|
||||
setup_locale();
|
||||
}
|
||||
|
||||
int tester_common::run(std::vector<std::string> args) {
|
||||
args.insert(args.begin(), toolname_);
|
||||
return tool::main_adapter(main_)(args, iol->get());
|
||||
}
|
||||
|
||||
int tester_common::run(std::initializer_list<std::string> args) {
|
||||
return run(std::vector<std::string>(args));
|
||||
}
|
||||
|
||||
int tester_common::run(std::string args) { return run(test::parse_args(args)); }
|
||||
|
||||
mkdwarfs_tester::mkdwarfs_tester(std::shared_ptr<test::os_access_mock> pos)
|
||||
: tester_common(tool::mkdwarfs_main, "mkdwarfs", std::move(pos)) {}
|
||||
|
||||
mkdwarfs_tester::mkdwarfs_tester()
|
||||
: mkdwarfs_tester(test::os_access_mock::create_test_instance()) {}
|
||||
|
||||
mkdwarfs_tester mkdwarfs_tester::create_empty() {
|
||||
return mkdwarfs_tester(std::make_shared<test::os_access_mock>());
|
||||
}
|
||||
|
||||
void mkdwarfs_tester::add_stream_logger(std::ostream& st,
|
||||
logger::level_type level) {
|
||||
lgr = std::make_unique<stream_logger>(std::make_shared<test::test_terminal>(),
|
||||
st, logger_options{.threshold = level});
|
||||
}
|
||||
|
||||
void mkdwarfs_tester::add_root_dir() {
|
||||
os->add("", {1, 040755, 1, 0, 0, 10, 42, 0, 0, 0});
|
||||
}
|
||||
|
||||
void mkdwarfs_tester::add_special_files() {
|
||||
static constexpr file_stat::off_type const size = 10;
|
||||
std::string data(size, 'x');
|
||||
os->add("suid", {1001, 0104755, 1, 0, 0, size, 0, 3333, 2222, 1111}, data);
|
||||
os->add("sgid", {1002, 0102755, 1, 0, 0, size, 0, 0, 0, 0}, data);
|
||||
os->add("sticky", {1003, 0101755, 1, 0, 0, size, 0, 0, 0, 0}, data);
|
||||
os->add("block", {1004, 060666, 1, 0, 0, 0, 77, 0, 0, 0}, std::string{});
|
||||
os->add("sock", {1005, 0140666, 1, 0, 0, 0, 0, 0, 0, 0}, std::string{});
|
||||
}
|
||||
|
||||
std::vector<std::pair<fs::path, std::string>>
|
||||
mkdwarfs_tester::add_random_file_tree(random_file_tree_options const& opt) {
|
||||
size_t max_size{128 * static_cast<size_t>(opt.avg_size)};
|
||||
std::mt19937_64 rng{42};
|
||||
std::exponential_distribution<> size_dist{1 / opt.avg_size};
|
||||
std::uniform_int_distribution<> path_comp_size_dist{0, opt.max_name_len};
|
||||
std::uniform_int_distribution<> invalid_dist{0, 1};
|
||||
std::vector<std::pair<fs::path, std::string>> paths;
|
||||
|
||||
auto random_path_component = [&] {
|
||||
auto size = path_comp_size_dist(rng);
|
||||
if (opt.with_invalid_utf8 && invalid_dist(rng) == 0) {
|
||||
return test::create_random_string(size, 96, 255, rng);
|
||||
}
|
||||
return test::create_random_string(size, 'A', 'Z', rng);
|
||||
};
|
||||
|
||||
test::lz_params text_lzp{};
|
||||
test::lz_params binary_lzp{};
|
||||
text_lzp.text_mode = true;
|
||||
binary_lzp.text_mode = false;
|
||||
text_lzp.seed = rng();
|
||||
binary_lzp.seed = rng();
|
||||
test::lz_synthetic_generator text_gen{text_lzp};
|
||||
test::lz_synthetic_generator binary_gen{binary_lzp};
|
||||
|
||||
for (int x = 0; x < opt.dimension; ++x) {
|
||||
fs::path d1{random_path_component() + std::to_string(x)};
|
||||
os->add_dir(d1);
|
||||
|
||||
for (int y = 0; y < opt.dimension; ++y) {
|
||||
fs::path d2{d1 / (random_path_component() + std::to_string(y))};
|
||||
os->add_dir(d2);
|
||||
|
||||
for (int z = 0; z < opt.dimension; ++z) {
|
||||
fs::path f{d2 / (random_path_component() + std::to_string(z))};
|
||||
auto const size =
|
||||
std::min(max_size, static_cast<size_t>(size_dist(rng)));
|
||||
std::string data;
|
||||
|
||||
auto const choice = rng() % 4;
|
||||
switch (choice) {
|
||||
case 0:
|
||||
data = test::create_random_string(size, rng);
|
||||
break;
|
||||
case 1:
|
||||
data = test::loremipsum(size);
|
||||
break;
|
||||
case 3:
|
||||
data = text_gen.generate(size);
|
||||
break;
|
||||
case 4:
|
||||
data = binary_gen.generate(size);
|
||||
break;
|
||||
}
|
||||
|
||||
os->add_file(f, data);
|
||||
paths.emplace_back(f, data);
|
||||
|
||||
if (opt.with_errors) {
|
||||
auto failpath = fs::path{"/"} / f;
|
||||
switch (rng() % 8) {
|
||||
case 0:
|
||||
os->set_access_fail(failpath);
|
||||
[[fallthrough]];
|
||||
case 1:
|
||||
case 2:
|
||||
os->set_map_file_error(
|
||||
failpath,
|
||||
std::make_exception_ptr(std::runtime_error("map_file_error")),
|
||||
rng() % 4);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
void mkdwarfs_tester::add_test_file_tree() {
|
||||
for (auto const& [stat, name] : test::test_dirtree()) {
|
||||
auto path = name.substr(name.size() == 5 ? 5 : 6);
|
||||
|
||||
switch (stat.type()) {
|
||||
case posix_file_type::regular:
|
||||
os->add(path, stat,
|
||||
[size = stat.size] { return test::loremipsum(size); });
|
||||
break;
|
||||
case posix_file_type::symlink:
|
||||
os->add(path, stat, test::loremipsum(stat.size));
|
||||
break;
|
||||
default:
|
||||
os->add(path, stat);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reader::filesystem_v2
|
||||
mkdwarfs_tester::fs_from_data(std::string data,
|
||||
reader::filesystem_options const& opt) {
|
||||
if (!lgr) {
|
||||
lgr = std::make_unique<test::test_logger>();
|
||||
}
|
||||
auto mm = std::make_shared<test::mmap_mock>(std::move(data));
|
||||
return reader::filesystem_v2(*lgr, *os, mm, opt);
|
||||
}
|
||||
|
||||
reader::filesystem_v2
|
||||
mkdwarfs_tester::fs_from_file(std::string path,
|
||||
reader::filesystem_options const& opt) {
|
||||
auto fsimage = fa->get_file(path);
|
||||
if (!fsimage) {
|
||||
throw std::runtime_error("file not found: " + path);
|
||||
}
|
||||
return fs_from_data(std::move(fsimage.value()), opt);
|
||||
}
|
||||
|
||||
reader::filesystem_v2
|
||||
mkdwarfs_tester::fs_from_stdout(reader::filesystem_options const& opt) {
|
||||
return fs_from_data(out(), opt);
|
||||
}
|
||||
|
||||
dwarfsck_tester::dwarfsck_tester(std::shared_ptr<test::os_access_mock> pos)
|
||||
: tester_common(tool::dwarfsck_main, "dwarfsck", std::move(pos)) {}
|
||||
|
||||
dwarfsck_tester::dwarfsck_tester()
|
||||
: dwarfsck_tester(std::make_shared<test::os_access_mock>()) {}
|
||||
|
||||
dwarfsck_tester dwarfsck_tester::create_with_image(std::string image) {
|
||||
auto os = std::make_shared<test::os_access_mock>();
|
||||
os->add("", {1, 040755, 1, 0, 0, 10, 42, 0, 0, 0});
|
||||
os->add_file("image.dwarfs", std::move(image));
|
||||
return dwarfsck_tester(std::move(os));
|
||||
}
|
||||
|
||||
dwarfsck_tester dwarfsck_tester::create_with_image() {
|
||||
return create_with_image(build_test_image());
|
||||
}
|
||||
|
||||
dwarfsextract_tester::dwarfsextract_tester(
|
||||
std::shared_ptr<test::os_access_mock> pos)
|
||||
: tester_common(tool::dwarfsextract_main, "dwarfsextract", std::move(pos)) {
|
||||
}
|
||||
|
||||
dwarfsextract_tester::dwarfsextract_tester()
|
||||
: dwarfsextract_tester(std::make_shared<test::os_access_mock>()) {}
|
||||
|
||||
dwarfsextract_tester
|
||||
dwarfsextract_tester::create_with_image(std::string image) {
|
||||
auto os = std::make_shared<test::os_access_mock>();
|
||||
os->add("", {1, 040755, 1, 0, 0, 10, 42, 0, 0, 0});
|
||||
os->add_file("image.dwarfs", std::move(image));
|
||||
return dwarfsextract_tester(std::move(os));
|
||||
}
|
||||
|
||||
dwarfsextract_tester dwarfsextract_tester::create_with_image() {
|
||||
return create_with_image(build_test_image());
|
||||
}
|
||||
|
||||
int mkdwarfs_main_test::run(std::vector<std::string> args) {
|
||||
args.insert(args.begin(), "mkdwarfs");
|
||||
return tool::main_adapter(tool::mkdwarfs_main)(args, iol->get());
|
||||
}
|
||||
|
||||
int dwarfsck_main_test::run(std::vector<std::string> args) {
|
||||
args.insert(args.begin(), "dwarfsck");
|
||||
return tool::main_adapter(tool::dwarfsck_main)(args, iol->get());
|
||||
}
|
||||
|
||||
int dwarfsextract_main_test::run(std::vector<std::string> args) {
|
||||
args.insert(args.begin(), "dwarfsextract");
|
||||
return tool::main_adapter(tool::dwarfsextract_main)(args, iol->get());
|
||||
}
|
||||
|
||||
std::string build_test_image(std::vector<std::string> extra_args,
|
||||
std::map<std::string, std::string> extra_files) {
|
||||
mkdwarfs_tester t;
|
||||
for (auto const& [name, contents] : extra_files) {
|
||||
t.fa->set_file(name, contents);
|
||||
}
|
||||
std::vector<std::string> args = {"-i", "/", "-o", "-"};
|
||||
args.insert(args.end(), extra_args.begin(), extra_args.end());
|
||||
if (t.run(args) != 0) {
|
||||
throw std::runtime_error("failed to build test image:\n" + t.err());
|
||||
}
|
||||
return t.out();
|
||||
}
|
||||
|
||||
std::tuple<std::optional<reader::filesystem_v2>, mkdwarfs_tester>
|
||||
build_with_args(std::vector<std::string> opt_args) {
|
||||
std::string const image_file = "test.dwarfs";
|
||||
mkdwarfs_tester t;
|
||||
std::vector<std::string> args = {"-i", "/", "-o", image_file};
|
||||
args.insert(args.end(), opt_args.begin(), opt_args.end());
|
||||
if (t.run(args) != 0) {
|
||||
return {std::nullopt, std::move(t)};
|
||||
}
|
||||
return {t.fs_from_file(image_file), std::move(t)};
|
||||
}
|
||||
|
||||
std::set<uint64_t> get_all_fs_times(reader::filesystem_v2 const& fs) {
|
||||
std::set<uint64_t> times;
|
||||
fs.walk([&](auto const& e) {
|
||||
auto st = fs.getattr(e.inode());
|
||||
times.insert(st.atime());
|
||||
times.insert(st.ctime());
|
||||
times.insert(st.mtime());
|
||||
});
|
||||
return times;
|
||||
}
|
||||
|
||||
std::set<uint64_t> get_all_fs_uids(reader::filesystem_v2 const& fs) {
|
||||
std::set<uint64_t> uids;
|
||||
fs.walk([&](auto const& e) {
|
||||
auto st = fs.getattr(e.inode());
|
||||
uids.insert(st.uid());
|
||||
});
|
||||
return uids;
|
||||
}
|
||||
|
||||
std::set<uint64_t> get_all_fs_gids(reader::filesystem_v2 const& fs) {
|
||||
std::set<uint64_t> gids;
|
||||
fs.walk([&](auto const& e) {
|
||||
auto st = fs.getattr(e.inode());
|
||||
gids.insert(st.gid());
|
||||
});
|
||||
return gids;
|
||||
}
|
||||
|
||||
} // namespace dwarfs::test
|
214
test/test_tool_main_tester.h
Normal file
214
test/test_tool_main_tester.h
Normal file
@ -0,0 +1,214 @@
|
||||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/**
|
||||
* \author Marcus Holland-Moritz (github@mhxnet.de)
|
||||
* \copyright Copyright (c) Marcus Holland-Moritz
|
||||
*
|
||||
* This file is part of dwarfs.
|
||||
*
|
||||
* dwarfs is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* dwarfs is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with dwarfs. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <initializer_list>
|
||||
#include <iosfwd>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <random>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <dwarfs/config.h>
|
||||
#include <dwarfs/logger.h>
|
||||
#include <dwarfs/reader/filesystem_options.h>
|
||||
#include <dwarfs/reader/filesystem_v2.h>
|
||||
#include <dwarfs/tool/main_adapter.h>
|
||||
|
||||
#include "loremipsum.h"
|
||||
#include "lz_synthetic_generator.h"
|
||||
#include "mmap_mock.h"
|
||||
#include "test_helpers.h"
|
||||
#include "test_logger.h"
|
||||
|
||||
namespace dwarfs::test {
|
||||
|
||||
// TODO: this is a workaround for older Clang versions
|
||||
struct fs_path_hash {
|
||||
auto operator()(std::filesystem::path const& p) const noexcept {
|
||||
return std::filesystem::hash_value(p);
|
||||
}
|
||||
};
|
||||
|
||||
extern std::filesystem::path const test_dir;
|
||||
extern std::filesystem::path const audio_data_dir;
|
||||
extern std::filesystem::path const fits_data_dir;
|
||||
|
||||
constexpr std::array<std::string_view, 6> const log_level_strings{
|
||||
"error", "warn", "info", "verbose", "debug", "trace"};
|
||||
|
||||
enum class input_mode {
|
||||
from_file,
|
||||
from_stdin,
|
||||
};
|
||||
|
||||
constexpr std::array input_modes{
|
||||
input_mode::from_file,
|
||||
input_mode::from_stdin,
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, input_mode m);
|
||||
|
||||
enum class path_type {
|
||||
relative,
|
||||
absolute,
|
||||
mixed,
|
||||
};
|
||||
|
||||
constexpr std::array path_types{path_type::relative, path_type::absolute,
|
||||
path_type::mixed};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, path_type m);
|
||||
|
||||
class tester_common {
|
||||
public:
|
||||
using main_ptr_t = tool::main_adapter::main_fn_type;
|
||||
|
||||
tester_common(main_ptr_t mp, std::string toolname,
|
||||
std::shared_ptr<test::os_access_mock> pos);
|
||||
|
||||
int run(std::vector<std::string> args);
|
||||
int run(std::initializer_list<std::string> args);
|
||||
int run(std::string args);
|
||||
|
||||
std::string out() const { return iol->out(); }
|
||||
std::string err() const { return iol->err(); }
|
||||
|
||||
std::shared_ptr<test::test_file_access> fa;
|
||||
std::shared_ptr<test::os_access_mock> os;
|
||||
std::unique_ptr<test::test_iolayer> iol;
|
||||
|
||||
private:
|
||||
main_ptr_t main_;
|
||||
std::string toolname_;
|
||||
};
|
||||
|
||||
struct random_file_tree_options {
|
||||
double avg_size{4096.0};
|
||||
int dimension{20};
|
||||
int max_name_len{50};
|
||||
bool with_errors{false};
|
||||
bool with_invalid_utf8{false};
|
||||
};
|
||||
|
||||
constexpr auto default_fs_opts = reader::filesystem_options{
|
||||
.block_cache = {.max_bytes = 256 * 1024,
|
||||
.sequential_access_detector_threshold = 4},
|
||||
.metadata = {.check_consistency = true}};
|
||||
|
||||
class mkdwarfs_tester : public tester_common {
|
||||
public:
|
||||
mkdwarfs_tester();
|
||||
explicit mkdwarfs_tester(std::shared_ptr<test::os_access_mock> pos);
|
||||
|
||||
static mkdwarfs_tester create_empty();
|
||||
|
||||
void add_stream_logger(std::ostream& st,
|
||||
logger::level_type level = logger::VERBOSE);
|
||||
|
||||
void add_root_dir();
|
||||
void add_special_files();
|
||||
void add_test_file_tree();
|
||||
|
||||
std::vector<std::pair<std::filesystem::path, std::string>>
|
||||
add_random_file_tree(
|
||||
random_file_tree_options const& opt = random_file_tree_options{});
|
||||
|
||||
reader::filesystem_v2
|
||||
fs_from_data(std::string data,
|
||||
reader::filesystem_options const& opt = default_fs_opts);
|
||||
|
||||
reader::filesystem_v2
|
||||
fs_from_file(std::string path,
|
||||
reader::filesystem_options const& opt = default_fs_opts);
|
||||
|
||||
reader::filesystem_v2
|
||||
fs_from_stdout(reader::filesystem_options const& opt = default_fs_opts);
|
||||
|
||||
std::unique_ptr<logger> lgr;
|
||||
};
|
||||
|
||||
std::string
|
||||
build_test_image(std::vector<std::string> extra_args = {},
|
||||
std::map<std::string, std::string> extra_files = {});
|
||||
|
||||
class dwarfsck_tester : public tester_common {
|
||||
public:
|
||||
dwarfsck_tester();
|
||||
explicit dwarfsck_tester(std::shared_ptr<test::os_access_mock> pos);
|
||||
|
||||
static dwarfsck_tester create_with_image();
|
||||
static dwarfsck_tester create_with_image(std::string image);
|
||||
};
|
||||
|
||||
class dwarfsextract_tester : public tester_common {
|
||||
public:
|
||||
dwarfsextract_tester();
|
||||
explicit dwarfsextract_tester(std::shared_ptr<test::os_access_mock> pos);
|
||||
|
||||
static dwarfsextract_tester create_with_image();
|
||||
static dwarfsextract_tester create_with_image(std::string image);
|
||||
};
|
||||
|
||||
std::tuple<std::optional<reader::filesystem_v2>, mkdwarfs_tester>
|
||||
build_with_args(std::vector<std::string> opt_args = {});
|
||||
|
||||
std::set<uint64_t> get_all_fs_times(reader::filesystem_v2 const& fs);
|
||||
std::set<uint64_t> get_all_fs_uids(reader::filesystem_v2 const& fs);
|
||||
std::set<uint64_t> get_all_fs_gids(reader::filesystem_v2 const& fs);
|
||||
|
||||
class tool_main_test : public testing::Test {
|
||||
public:
|
||||
void SetUp() override;
|
||||
void TearDown() override;
|
||||
|
||||
std::string out() const { return iol->out(); }
|
||||
std::string err() const { return iol->err(); }
|
||||
|
||||
std::unique_ptr<test::test_iolayer> iol;
|
||||
};
|
||||
|
||||
class mkdwarfs_main_test : public tool_main_test {
|
||||
public:
|
||||
int run(std::vector<std::string> args);
|
||||
};
|
||||
|
||||
class dwarfsck_main_test : public tool_main_test {
|
||||
public:
|
||||
int run(std::vector<std::string> args);
|
||||
};
|
||||
|
||||
class dwarfsextract_main_test : public tool_main_test {
|
||||
public:
|
||||
int run(std::vector<std::string> args);
|
||||
};
|
||||
|
||||
} // namespace dwarfs::test
|
@ -22,16 +22,10 @@
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
#include <regex>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <archive.h>
|
||||
#include <archive_entry.h>
|
||||
@ -51,451 +45,22 @@
|
||||
#include <range/v3/view/join.hpp>
|
||||
#include <range/v3/view/map.hpp>
|
||||
|
||||
#include <dwarfs/config.h>
|
||||
#include <dwarfs/file_util.h>
|
||||
#include <dwarfs/history.h>
|
||||
#include <dwarfs/logger.h>
|
||||
#include <dwarfs/reader/filesystem_options.h>
|
||||
#include <dwarfs/reader/filesystem_v2.h>
|
||||
#include <dwarfs/reader/fsinfo_options.h>
|
||||
#include <dwarfs/reader/iovec_read_buf.h>
|
||||
#include <dwarfs/sorted_array_map.h>
|
||||
#include <dwarfs/string.h>
|
||||
#include <dwarfs/tool/main_adapter.h>
|
||||
#include <dwarfs/util.h>
|
||||
#include <dwarfs_tool_main.h>
|
||||
|
||||
#include "filter_test_data.h"
|
||||
#include "loremipsum.h"
|
||||
#include "lz_synthetic_generator.h"
|
||||
#include "mmap_mock.h"
|
||||
#include "test_helpers.h"
|
||||
#include "test_logger.h"
|
||||
#include "test_tool_main_tester.h"
|
||||
|
||||
using namespace dwarfs;
|
||||
using namespace dwarfs::test;
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
using tool::iolayer;
|
||||
|
||||
namespace {
|
||||
|
||||
// TODO: this is a workaround for older Clang versions
|
||||
struct fs_path_hash {
|
||||
auto operator()(std::filesystem::path const& p) const noexcept {
|
||||
return std::filesystem::hash_value(p);
|
||||
}
|
||||
};
|
||||
|
||||
auto test_dir = fs::path(TEST_DATA_DIR).make_preferred();
|
||||
auto audio_data_dir = test_dir / "pcmaudio";
|
||||
auto fits_data_dir = test_dir / "fits";
|
||||
|
||||
constexpr std::array<std::string_view, 6> const log_level_strings{
|
||||
"error", "warn", "info", "verbose", "debug", "trace"};
|
||||
|
||||
enum class input_mode {
|
||||
from_file,
|
||||
from_stdin,
|
||||
};
|
||||
|
||||
constexpr std::array input_modes{
|
||||
input_mode::from_file,
|
||||
input_mode::from_stdin,
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, input_mode m) {
|
||||
switch (m) {
|
||||
case input_mode::from_file:
|
||||
os << "from_file";
|
||||
break;
|
||||
case input_mode::from_stdin:
|
||||
os << "from_stdin";
|
||||
break;
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
enum class path_type {
|
||||
relative,
|
||||
absolute,
|
||||
mixed,
|
||||
};
|
||||
|
||||
constexpr std::array path_types{path_type::relative, path_type::absolute,
|
||||
path_type::mixed};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, path_type m) {
|
||||
switch (m) {
|
||||
case path_type::relative:
|
||||
os << "relative";
|
||||
break;
|
||||
case path_type::absolute:
|
||||
os << "absolute";
|
||||
break;
|
||||
case path_type::mixed:
|
||||
os << "mixed";
|
||||
break;
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
struct locale_setup_helper {
|
||||
locale_setup_helper() { setup_default_locale(); }
|
||||
};
|
||||
|
||||
void setup_locale() { static locale_setup_helper helper; }
|
||||
|
||||
class tool_main_test : public testing::Test {
|
||||
public:
|
||||
void SetUp() override {
|
||||
setup_locale();
|
||||
iol = std::make_unique<test::test_iolayer>();
|
||||
}
|
||||
|
||||
void TearDown() override { iol.reset(); }
|
||||
|
||||
std::string out() const { return iol->out(); }
|
||||
std::string err() const { return iol->err(); }
|
||||
|
||||
std::unique_ptr<test::test_iolayer> iol;
|
||||
};
|
||||
|
||||
class tester_common {
|
||||
public:
|
||||
using main_ptr_t = tool::main_adapter::main_fn_type;
|
||||
|
||||
tester_common(main_ptr_t mp, std::string toolname,
|
||||
std::shared_ptr<test::os_access_mock> pos)
|
||||
: fa{std::make_shared<test::test_file_access>()}
|
||||
, os{std::move(pos)}
|
||||
, iol{std::make_unique<test::test_iolayer>(os, fa)}
|
||||
, main_{mp}
|
||||
, toolname_{std::move(toolname)} {
|
||||
setup_locale();
|
||||
}
|
||||
|
||||
int run(std::vector<std::string> args) {
|
||||
args.insert(args.begin(), toolname_);
|
||||
return tool::main_adapter(main_)(args, iol->get());
|
||||
}
|
||||
|
||||
int run(std::initializer_list<std::string> args) {
|
||||
return run(std::vector<std::string>(args));
|
||||
}
|
||||
|
||||
int run(std::string args) { return run(test::parse_args(args)); }
|
||||
|
||||
std::string out() const { return iol->out(); }
|
||||
std::string err() const { return iol->err(); }
|
||||
|
||||
std::shared_ptr<test::test_file_access> fa;
|
||||
std::shared_ptr<test::os_access_mock> os;
|
||||
std::unique_ptr<test::test_iolayer> iol;
|
||||
|
||||
private:
|
||||
main_ptr_t main_;
|
||||
std::string toolname_;
|
||||
};
|
||||
|
||||
struct random_file_tree_options {
|
||||
double avg_size{4096.0};
|
||||
int dimension{20};
|
||||
int max_name_len{50};
|
||||
bool with_errors{false};
|
||||
bool with_invalid_utf8{false};
|
||||
};
|
||||
|
||||
constexpr auto default_fs_opts = reader::filesystem_options{
|
||||
.block_cache = {.max_bytes = 256 * 1024,
|
||||
.sequential_access_detector_threshold = 4},
|
||||
.metadata = {.check_consistency = true}};
|
||||
|
||||
class mkdwarfs_tester : public tester_common {
|
||||
public:
|
||||
mkdwarfs_tester(std::shared_ptr<test::os_access_mock> pos)
|
||||
: tester_common(tool::mkdwarfs_main, "mkdwarfs", std::move(pos)) {}
|
||||
|
||||
mkdwarfs_tester()
|
||||
: mkdwarfs_tester(test::os_access_mock::create_test_instance()) {}
|
||||
|
||||
static mkdwarfs_tester create_empty() {
|
||||
return mkdwarfs_tester(std::make_shared<test::os_access_mock>());
|
||||
}
|
||||
|
||||
void add_stream_logger(std::ostream& st,
|
||||
logger::level_type level = logger::VERBOSE) {
|
||||
lgr =
|
||||
std::make_unique<stream_logger>(std::make_shared<test::test_terminal>(),
|
||||
st, logger_options{.threshold = level});
|
||||
}
|
||||
|
||||
void add_root_dir() { os->add("", {1, 040755, 1, 0, 0, 10, 42, 0, 0, 0}); }
|
||||
|
||||
void add_special_files() {
|
||||
static constexpr file_stat::off_type const size = 10;
|
||||
std::string data(size, 'x');
|
||||
os->add("suid", {1001, 0104755, 1, 0, 0, size, 0, 3333, 2222, 1111}, data);
|
||||
os->add("sgid", {1002, 0102755, 1, 0, 0, size, 0, 0, 0, 0}, data);
|
||||
os->add("sticky", {1003, 0101755, 1, 0, 0, size, 0, 0, 0, 0}, data);
|
||||
os->add("block", {1004, 060666, 1, 0, 0, 0, 77, 0, 0, 0}, std::string{});
|
||||
os->add("sock", {1005, 0140666, 1, 0, 0, 0, 0, 0, 0, 0}, std::string{});
|
||||
}
|
||||
|
||||
std::vector<std::pair<fs::path, std::string>> add_random_file_tree(
|
||||
random_file_tree_options const& opt = random_file_tree_options{}) {
|
||||
size_t max_size{128 * static_cast<size_t>(opt.avg_size)};
|
||||
std::mt19937_64 rng{42};
|
||||
std::exponential_distribution<> size_dist{1 / opt.avg_size};
|
||||
std::uniform_int_distribution<> path_comp_size_dist{0, opt.max_name_len};
|
||||
std::uniform_int_distribution<> invalid_dist{0, 1};
|
||||
std::vector<std::pair<fs::path, std::string>> paths;
|
||||
|
||||
auto random_path_component = [&] {
|
||||
auto size = path_comp_size_dist(rng);
|
||||
if (opt.with_invalid_utf8 && invalid_dist(rng) == 0) {
|
||||
return test::create_random_string(size, 96, 255, rng);
|
||||
}
|
||||
return test::create_random_string(size, 'A', 'Z', rng);
|
||||
};
|
||||
|
||||
test::lz_params text_lzp{};
|
||||
test::lz_params binary_lzp{};
|
||||
text_lzp.text_mode = true;
|
||||
binary_lzp.text_mode = false;
|
||||
text_lzp.seed = rng();
|
||||
binary_lzp.seed = rng();
|
||||
test::lz_synthetic_generator text_gen{text_lzp};
|
||||
test::lz_synthetic_generator binary_gen{binary_lzp};
|
||||
|
||||
for (int x = 0; x < opt.dimension; ++x) {
|
||||
fs::path d1{random_path_component() + std::to_string(x)};
|
||||
os->add_dir(d1);
|
||||
|
||||
for (int y = 0; y < opt.dimension; ++y) {
|
||||
fs::path d2{d1 / (random_path_component() + std::to_string(y))};
|
||||
os->add_dir(d2);
|
||||
|
||||
for (int z = 0; z < opt.dimension; ++z) {
|
||||
fs::path f{d2 / (random_path_component() + std::to_string(z))};
|
||||
auto const size =
|
||||
std::min(max_size, static_cast<size_t>(size_dist(rng)));
|
||||
std::string data;
|
||||
|
||||
auto const choice = rng() % 4;
|
||||
switch (choice) {
|
||||
case 0:
|
||||
data = test::create_random_string(size, rng);
|
||||
break;
|
||||
case 1:
|
||||
data = test::loremipsum(size);
|
||||
break;
|
||||
case 3:
|
||||
data = text_gen.generate(size);
|
||||
break;
|
||||
case 4:
|
||||
data = binary_gen.generate(size);
|
||||
break;
|
||||
}
|
||||
|
||||
os->add_file(f, data);
|
||||
paths.emplace_back(f, data);
|
||||
|
||||
if (opt.with_errors) {
|
||||
auto failpath = fs::path{"/"} / f;
|
||||
switch (rng() % 8) {
|
||||
case 0:
|
||||
os->set_access_fail(failpath);
|
||||
[[fallthrough]];
|
||||
case 1:
|
||||
case 2:
|
||||
os->set_map_file_error(
|
||||
failpath,
|
||||
std::make_exception_ptr(std::runtime_error("map_file_error")),
|
||||
rng() % 4);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
void add_test_file_tree() {
|
||||
for (auto const& [stat, name] : test::test_dirtree()) {
|
||||
auto path = name.substr(name.size() == 5 ? 5 : 6);
|
||||
|
||||
switch (stat.type()) {
|
||||
case posix_file_type::regular:
|
||||
os->add(path, stat,
|
||||
[size = stat.size] { return test::loremipsum(size); });
|
||||
break;
|
||||
case posix_file_type::symlink:
|
||||
os->add(path, stat, test::loremipsum(stat.size));
|
||||
break;
|
||||
default:
|
||||
os->add(path, stat);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reader::filesystem_v2
|
||||
fs_from_data(std::string data,
|
||||
reader::filesystem_options const& opt = default_fs_opts) {
|
||||
if (!lgr) {
|
||||
lgr = std::make_unique<test::test_logger>();
|
||||
}
|
||||
auto mm = std::make_shared<test::mmap_mock>(std::move(data));
|
||||
return reader::filesystem_v2(*lgr, *os, mm, opt);
|
||||
}
|
||||
|
||||
reader::filesystem_v2
|
||||
fs_from_file(std::string path,
|
||||
reader::filesystem_options const& opt = default_fs_opts) {
|
||||
auto fsimage = fa->get_file(path);
|
||||
if (!fsimage) {
|
||||
throw std::runtime_error("file not found: " + path);
|
||||
}
|
||||
return fs_from_data(std::move(fsimage.value()), opt);
|
||||
}
|
||||
|
||||
reader::filesystem_v2
|
||||
fs_from_stdout(reader::filesystem_options const& opt = default_fs_opts) {
|
||||
return fs_from_data(out(), opt);
|
||||
}
|
||||
|
||||
std::unique_ptr<logger> lgr;
|
||||
};
|
||||
|
||||
std::string
|
||||
build_test_image(std::vector<std::string> extra_args = {},
|
||||
std::map<std::string, std::string> extra_files = {}) {
|
||||
mkdwarfs_tester t;
|
||||
for (auto const& [name, contents] : extra_files) {
|
||||
t.fa->set_file(name, contents);
|
||||
}
|
||||
std::vector<std::string> args = {"-i", "/", "-o", "-"};
|
||||
args.insert(args.end(), extra_args.begin(), extra_args.end());
|
||||
if (t.run(args) != 0) {
|
||||
throw std::runtime_error("failed to build test image:\n" + t.err());
|
||||
}
|
||||
return t.out();
|
||||
}
|
||||
|
||||
class dwarfsck_tester : public tester_common {
|
||||
public:
|
||||
dwarfsck_tester(std::shared_ptr<test::os_access_mock> pos)
|
||||
: tester_common(tool::dwarfsck_main, "dwarfsck", std::move(pos)) {}
|
||||
|
||||
dwarfsck_tester()
|
||||
: dwarfsck_tester(std::make_shared<test::os_access_mock>()) {}
|
||||
|
||||
static dwarfsck_tester create_with_image(std::string image) {
|
||||
auto os = std::make_shared<test::os_access_mock>();
|
||||
os->add("", {1, 040755, 1, 0, 0, 10, 42, 0, 0, 0});
|
||||
os->add_file("image.dwarfs", std::move(image));
|
||||
return dwarfsck_tester(std::move(os));
|
||||
}
|
||||
|
||||
static dwarfsck_tester create_with_image() {
|
||||
return create_with_image(build_test_image());
|
||||
}
|
||||
};
|
||||
|
||||
class dwarfsextract_tester : public tester_common {
|
||||
public:
|
||||
dwarfsextract_tester(std::shared_ptr<test::os_access_mock> pos)
|
||||
: tester_common(tool::dwarfsextract_main, "dwarfsextract",
|
||||
std::move(pos)) {}
|
||||
|
||||
dwarfsextract_tester()
|
||||
: dwarfsextract_tester(std::make_shared<test::os_access_mock>()) {}
|
||||
|
||||
static dwarfsextract_tester create_with_image(std::string image) {
|
||||
auto os = std::make_shared<test::os_access_mock>();
|
||||
os->add("", {1, 040755, 1, 0, 0, 10, 42, 0, 0, 0});
|
||||
os->add_file("image.dwarfs", std::move(image));
|
||||
return dwarfsextract_tester(std::move(os));
|
||||
}
|
||||
|
||||
static dwarfsextract_tester create_with_image() {
|
||||
return create_with_image(build_test_image());
|
||||
}
|
||||
};
|
||||
|
||||
std::tuple<std::optional<reader::filesystem_v2>, mkdwarfs_tester>
|
||||
build_with_args(std::vector<std::string> opt_args = {}) {
|
||||
std::string const image_file = "test.dwarfs";
|
||||
mkdwarfs_tester t;
|
||||
std::vector<std::string> args = {"-i", "/", "-o", image_file};
|
||||
args.insert(args.end(), opt_args.begin(), opt_args.end());
|
||||
if (t.run(args) != 0) {
|
||||
return {std::nullopt, std::move(t)};
|
||||
}
|
||||
return {t.fs_from_file(image_file), std::move(t)};
|
||||
}
|
||||
|
||||
std::set<uint64_t> get_all_fs_times(reader::filesystem_v2 const& fs) {
|
||||
std::set<uint64_t> times;
|
||||
fs.walk([&](auto const& e) {
|
||||
auto st = fs.getattr(e.inode());
|
||||
times.insert(st.atime());
|
||||
times.insert(st.ctime());
|
||||
times.insert(st.mtime());
|
||||
});
|
||||
return times;
|
||||
}
|
||||
|
||||
std::set<uint64_t> get_all_fs_uids(reader::filesystem_v2 const& fs) {
|
||||
std::set<uint64_t> uids;
|
||||
fs.walk([&](auto const& e) {
|
||||
auto st = fs.getattr(e.inode());
|
||||
uids.insert(st.uid());
|
||||
});
|
||||
return uids;
|
||||
}
|
||||
|
||||
std::set<uint64_t> get_all_fs_gids(reader::filesystem_v2 const& fs) {
|
||||
std::set<uint64_t> gids;
|
||||
fs.walk([&](auto const& e) {
|
||||
auto st = fs.getattr(e.inode());
|
||||
gids.insert(st.gid());
|
||||
});
|
||||
return gids;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class mkdwarfs_main_test : public tool_main_test {
|
||||
public:
|
||||
int run(std::vector<std::string> args) {
|
||||
args.insert(args.begin(), "mkdwarfs");
|
||||
return tool::main_adapter(tool::mkdwarfs_main)(args, iol->get());
|
||||
}
|
||||
};
|
||||
|
||||
class dwarfsck_main_test : public tool_main_test {
|
||||
public:
|
||||
int run(std::vector<std::string> args) {
|
||||
args.insert(args.begin(), "dwarfsck");
|
||||
return tool::main_adapter(tool::dwarfsck_main)(args, iol->get());
|
||||
}
|
||||
};
|
||||
|
||||
class dwarfsextract_main_test : public tool_main_test {
|
||||
public:
|
||||
int run(std::vector<std::string> args) {
|
||||
args.insert(args.begin(), "dwarfsextract");
|
||||
return tool::main_adapter(tool::dwarfsextract_main)(args, iol->get());
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(mkdwarfs_main_test, no_cmdline_args) {
|
||||
auto exit_code = run({});
|
||||
EXPECT_EQ(exit_code, 0);
|
||||
|
Loading…
x
Reference in New Issue
Block a user