diff --git a/CMakeLists.txt b/CMakeLists.txt index 80ab8d33..ae9f804c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/test/test_tool_main_tester.cpp b/test/test_tool_main_tester.cpp new file mode 100644 index 00000000..d387e779 --- /dev/null +++ b/test/test_tool_main_tester.cpp @@ -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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include + +#include + +#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(); +} + +void tool_main_test::TearDown() { iol.reset(); } + +tester_common::tester_common(main_ptr_t mp, std::string toolname, + std::shared_ptr pos) + : fa{std::make_shared()} + , os{std::move(pos)} + , iol{std::make_unique(os, fa)} + , main_{mp} + , toolname_{std::move(toolname)} { + setup_locale(); +} + +int tester_common::run(std::vector args) { + args.insert(args.begin(), toolname_); + return tool::main_adapter(main_)(args, iol->get()); +} + +int tester_common::run(std::initializer_list args) { + return run(std::vector(args)); +} + +int tester_common::run(std::string args) { return run(test::parse_args(args)); } + +mkdwarfs_tester::mkdwarfs_tester(std::shared_ptr 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()); +} + +void mkdwarfs_tester::add_stream_logger(std::ostream& st, + logger::level_type level) { + lgr = std::make_unique(std::make_shared(), + 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> +mkdwarfs_tester::add_random_file_tree(random_file_tree_options const& opt) { + size_t max_size{128 * static_cast(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> 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_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(); + } + auto mm = std::make_shared(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 pos) + : tester_common(tool::dwarfsck_main, "dwarfsck", std::move(pos)) {} + +dwarfsck_tester::dwarfsck_tester() + : dwarfsck_tester(std::make_shared()) {} + +dwarfsck_tester dwarfsck_tester::create_with_image(std::string image) { + auto os = std::make_shared(); + 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 pos) + : tester_common(tool::dwarfsextract_main, "dwarfsextract", std::move(pos)) { +} + +dwarfsextract_tester::dwarfsextract_tester() + : dwarfsextract_tester(std::make_shared()) {} + +dwarfsextract_tester +dwarfsextract_tester::create_with_image(std::string image) { + auto os = std::make_shared(); + 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 args) { + args.insert(args.begin(), "mkdwarfs"); + return tool::main_adapter(tool::mkdwarfs_main)(args, iol->get()); +} + +int dwarfsck_main_test::run(std::vector args) { + args.insert(args.begin(), "dwarfsck"); + return tool::main_adapter(tool::dwarfsck_main)(args, iol->get()); +} + +int dwarfsextract_main_test::run(std::vector args) { + args.insert(args.begin(), "dwarfsextract"); + return tool::main_adapter(tool::dwarfsextract_main)(args, iol->get()); +} + +std::string build_test_image(std::vector extra_args, + std::map extra_files) { + mkdwarfs_tester t; + for (auto const& [name, contents] : extra_files) { + t.fa->set_file(name, contents); + } + std::vector 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, mkdwarfs_tester> +build_with_args(std::vector opt_args) { + std::string const image_file = "test.dwarfs"; + mkdwarfs_tester t; + std::vector 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 get_all_fs_times(reader::filesystem_v2 const& fs) { + std::set 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 get_all_fs_uids(reader::filesystem_v2 const& fs) { + std::set uids; + fs.walk([&](auto const& e) { + auto st = fs.getattr(e.inode()); + uids.insert(st.uid()); + }); + return uids; +} + +std::set get_all_fs_gids(reader::filesystem_v2 const& fs) { + std::set gids; + fs.walk([&](auto const& e) { + auto st = fs.getattr(e.inode()); + gids.insert(st.gid()); + }); + return gids; +} + +} // namespace dwarfs::test diff --git a/test/test_tool_main_tester.h b/test/test_tool_main_tester.h new file mode 100644 index 00000000..b9d865b6 --- /dev/null +++ b/test/test_tool_main_tester.h @@ -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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#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 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 pos); + + int run(std::vector args); + int run(std::initializer_list args); + int run(std::string args); + + std::string out() const { return iol->out(); } + std::string err() const { return iol->err(); } + + std::shared_ptr fa; + std::shared_ptr os; + std::unique_ptr 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 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> + 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 lgr; +}; + +std::string +build_test_image(std::vector extra_args = {}, + std::map extra_files = {}); + +class dwarfsck_tester : public tester_common { + public: + dwarfsck_tester(); + explicit dwarfsck_tester(std::shared_ptr 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 pos); + + static dwarfsextract_tester create_with_image(); + static dwarfsextract_tester create_with_image(std::string image); +}; + +std::tuple, mkdwarfs_tester> +build_with_args(std::vector opt_args = {}); + +std::set get_all_fs_times(reader::filesystem_v2 const& fs); +std::set get_all_fs_uids(reader::filesystem_v2 const& fs); +std::set 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 iol; +}; + +class mkdwarfs_main_test : public tool_main_test { + public: + int run(std::vector args); +}; + +class dwarfsck_main_test : public tool_main_test { + public: + int run(std::vector args); +}; + +class dwarfsextract_main_test : public tool_main_test { + public: + int run(std::vector args); +}; + +} // namespace dwarfs::test diff --git a/test/tool_main_test.cpp b/test/tool_main_test.cpp index 046ab88d..e82e7036 100644 --- a/test/tool_main_test.cpp +++ b/test/tool_main_test.cpp @@ -22,16 +22,10 @@ */ #include -#include -#include #include -#include #include -#include -#include #include -#include #include #include @@ -51,451 +45,22 @@ #include #include -#include #include #include -#include -#include -#include #include #include #include #include -#include #include -#include #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 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(); - } - - void TearDown() override { iol.reset(); } - - std::string out() const { return iol->out(); } - std::string err() const { return iol->err(); } - - std::unique_ptr 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 pos) - : fa{std::make_shared()} - , os{std::move(pos)} - , iol{std::make_unique(os, fa)} - , main_{mp} - , toolname_{std::move(toolname)} { - setup_locale(); - } - - int run(std::vector args) { - args.insert(args.begin(), toolname_); - return tool::main_adapter(main_)(args, iol->get()); - } - - int run(std::initializer_list args) { - return run(std::vector(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 fa; - std::shared_ptr os; - std::unique_ptr 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 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()); - } - - void add_stream_logger(std::ostream& st, - logger::level_type level = logger::VERBOSE) { - lgr = - std::make_unique(std::make_shared(), - 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> add_random_file_tree( - random_file_tree_options const& opt = random_file_tree_options{}) { - size_t max_size{128 * static_cast(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> 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_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(); - } - auto mm = std::make_shared(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 lgr; -}; - -std::string -build_test_image(std::vector extra_args = {}, - std::map extra_files = {}) { - mkdwarfs_tester t; - for (auto const& [name, contents] : extra_files) { - t.fa->set_file(name, contents); - } - std::vector 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 pos) - : tester_common(tool::dwarfsck_main, "dwarfsck", std::move(pos)) {} - - dwarfsck_tester() - : dwarfsck_tester(std::make_shared()) {} - - static dwarfsck_tester create_with_image(std::string image) { - auto os = std::make_shared(); - 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 pos) - : tester_common(tool::dwarfsextract_main, "dwarfsextract", - std::move(pos)) {} - - dwarfsextract_tester() - : dwarfsextract_tester(std::make_shared()) {} - - static dwarfsextract_tester create_with_image(std::string image) { - auto os = std::make_shared(); - 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, mkdwarfs_tester> -build_with_args(std::vector opt_args = {}) { - std::string const image_file = "test.dwarfs"; - mkdwarfs_tester t; - std::vector 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 get_all_fs_times(reader::filesystem_v2 const& fs) { - std::set 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 get_all_fs_uids(reader::filesystem_v2 const& fs) { - std::set uids; - fs.walk([&](auto const& e) { - auto st = fs.getattr(e.inode()); - uids.insert(st.uid()); - }); - return uids; -} - -std::set get_all_fs_gids(reader::filesystem_v2 const& fs) { - std::set 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 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 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 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);