/* 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 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "test_helpers.h" using namespace dwarfs; using namespace dwarfs::tool; using namespace std::string_literals; namespace { struct tool_defs { manpage::document doc; main_adapter::main_fn_type main; std::string_view help_option; bool is_fuse; }; std::map const tools = { #ifdef DWARFS_WITH_TOOLS {"mkdwarfs", {manpage::get_mkdwarfs_manpage(), mkdwarfs_main, "-H", false}}, {"dwarfsck", {manpage::get_dwarfsck_manpage(), dwarfsck_main, "-h", false}}, {"dwarfsextract", {manpage::get_dwarfsextract_manpage(), dwarfsextract_main, "-h", false}}, #endif #ifdef DWARFS_WITH_FUSE_DRIVER {"dwarfs", {manpage::get_dwarfs_manpage(), dwarfs_main, "-h", true}}, #endif }; auto const render_tests = ranges::views::keys(tools) | ranges::to; std::array const coverage_tests{ #ifdef DWARFS_WITH_TOOLS "mkdwarfs"s, "dwarfsck"s, "dwarfsextract"s, #endif #ifdef DWARFS_WITH_FUSE_DRIVER #ifndef DWARFS_TEST_RUNNING_ON_ASAN // FUSE driver is leaky, so we don't run this test under ASAN "dwarfs"s, #endif #endif }; } // namespace class manpage_render_test : public ::testing::TestWithParam> {}; TEST_P(manpage_render_test, basic) { auto [name, color] = GetParam(); auto doc = tools.at(name).doc; for (size_t width = 20; width <= 200; width += 1) { auto out = render_manpage(doc, width, color); EXPECT_GT(out.size(), 1000); EXPECT_THAT(out, ::testing::HasSubstr(name)); EXPECT_THAT(out, ::testing::HasSubstr("SYNOPSIS")); EXPECT_THAT(out, ::testing::HasSubstr("DESCRIPTION")); EXPECT_THAT(out, ::testing::HasSubstr("AUTHOR")); EXPECT_THAT(out, ::testing::HasSubstr("COPYRIGHT")); } } INSTANTIATE_TEST_SUITE_P(dwarfs, manpage_render_test, ::testing::Combine(::testing::ValuesIn(render_tests), ::testing::Bool())); namespace { std::regex const boost_po_option{R"(\n\s+(-(\w)\s+\[\s+)?--(\w[\w-]*\w))"}; std::regex const manpage_option{R"(\n\s+(-(\w),\s+)?--(\w[\w-]*\w))"}; std::regex const fuse_option{R"(\n\s+-o\s+([\w()]+))"}; std::map parse_options(std::string const& text, std::regex const& re, bool is_fuse) { std::map options; auto opts_begin = std::sregex_iterator(text.begin(), text.end(), re); auto opts_end = std::sregex_iterator(); for (auto it = opts_begin; it != opts_end; ++it) { auto match = *it; if (is_fuse) { auto opt = match[1].str(); if (!options.emplace(opt, std::string{}).second) { throw std::runtime_error("duplicate option definition for " + opt); } } else { auto short_opt = match[2].str(); auto long_opt = match[3].str(); if (auto it = options.find(long_opt); it != options.end()) { if (!it->second.empty()) { if (short_opt.empty()) { continue; } else { throw std::runtime_error("duplicate option definition for " + long_opt); } } } options[long_opt] = short_opt; } } return options; } } // namespace class manpage_coverage_test : public ::testing::TestWithParam {}; TEST_P(manpage_coverage_test, options) { auto tool_name = GetParam(); auto const& tool = tools.at(tool_name); auto man = render_manpage(tool.doc, 80, false); test::test_iolayer iol; std::array const args{tool_name, tool.help_option}; auto rv = main_adapter{tool.main}(args, iol.get()); #ifndef _WIN32 // WinFSP exits with a non-zero code when displaying usage :-/ ASSERT_EQ(0, rv) << tool_name << " " << tool.help_option << " failed"; #endif auto help_opts = parse_options( iol.out(), tool.is_fuse ? fuse_option : boost_po_option, tool.is_fuse); auto man_opts = parse_options( man, tool.is_fuse ? fuse_option : manpage_option, tool.is_fuse); if (tool.is_fuse) { man_opts.erase("allow_root"); man_opts.erase("allow_other"); } else { EXPECT_TRUE(help_opts.contains("help")) << tool_name << " missing help option"; } for (auto const& [opt, short_opt] : help_opts) { auto it = man_opts.find(opt); if (it == man_opts.end()) { FAIL() << "option " << opt << " not documented for " << tool_name; } else { EXPECT_EQ(short_opt, it->second) << "short option mismatch for " << opt << " for " << tool_name; } } for (auto const& [opt, short_opt] : man_opts) { auto it = help_opts.find(opt); if (it == help_opts.end()) { FAIL() << "option " << opt << " is obsolete for " << tool_name; } } } INSTANTIATE_TEST_SUITE_P(dwarfs, manpage_coverage_test, ::testing::ValuesIn(coverage_tests)); TEST(pager_test, find_pager_program) { auto resolver = [](std::filesystem::path const& name) { std::map const programs = { {"less", "/whatever/bin/less"}, {"more", "/somewhere/bin/more"}, {"cat", "/bin/cat"}, }; for (auto const& [n, p] : programs) { if (name == n || name == p) { return p; } } return std::filesystem::path{}; }; { test::os_access_mock os; os.set_executable_resolver( [](std::filesystem::path const&) { return std::filesystem::path{}; }); { auto pager = find_pager_program(os); ASSERT_FALSE(pager); } os.set_executable_resolver(resolver); { auto pager = find_pager_program(os); ASSERT_TRUE(pager); EXPECT_EQ("/whatever/bin/less", pager->name); EXPECT_EQ(std::vector({"-R"}), pager->args); } { auto pager = find_pager_program(os); ASSERT_TRUE(pager); EXPECT_EQ("/whatever/bin/less", pager->name); EXPECT_EQ(std::vector({"-R"}), pager->args); } os.set_access_fail("more"); os.set_access_fail("less"); os.setenv("PAGER", "more"); { auto pager = find_pager_program(os); ASSERT_TRUE(pager); EXPECT_EQ("/somewhere/bin/more", pager->name); EXPECT_TRUE(pager->args.empty()); } os.setenv("PAGER", "less"); { auto pager = find_pager_program(os); ASSERT_TRUE(pager); EXPECT_EQ("/whatever/bin/less", pager->name); EXPECT_TRUE(pager->args.empty()); } os.setenv("PAGER", "cat"); { auto pager = find_pager_program(os); ASSERT_FALSE(pager); } os.setenv("PAGER", "/bla/foo"); { auto pager = find_pager_program(os); ASSERT_TRUE(pager); EXPECT_EQ("/bla/foo", pager->name); EXPECT_TRUE(pager->args.empty()); } os.setenv("PAGER", R"("/bla/foo")"); { auto pager = find_pager_program(os); ASSERT_TRUE(pager); EXPECT_EQ("/bla/foo", pager->name); EXPECT_TRUE(pager->args.empty()); } } }