mirror of
https://github.com/mhx/dwarfs.git
synced 2025-09-19 09:17:21 -04:00
test: integrate manpage coverage tests and remove check_manpage script
This commit is contained in:
parent
34cdbf5056
commit
2f08968187
@ -1,68 +0,0 @@
|
||||
#!/bin/env python3
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import termcolor
|
||||
|
||||
|
||||
def extract_options(text):
|
||||
options = set()
|
||||
|
||||
for line in text.splitlines():
|
||||
match = re.search(r"--(\w[\w-]*)", line)
|
||||
if match:
|
||||
options.add(match.group(1))
|
||||
|
||||
return options
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 3 or len(sys.argv) > 4:
|
||||
print("Usage: check_manpage.py <manpage> <program> [<help-arg>]")
|
||||
sys.exit(1)
|
||||
|
||||
manpage = sys.argv[1]
|
||||
program = sys.argv[2]
|
||||
help_arg = sys.argv[3] if len(sys.argv) == 4 else "--help"
|
||||
|
||||
manpage_basename = os.path.basename(manpage)
|
||||
|
||||
result = subprocess.run([program, help_arg], capture_output=True)
|
||||
program_help = (result.stdout + result.stderr).decode("utf-8")
|
||||
manpage_text = open(manpage).read()
|
||||
|
||||
program_options = extract_options(program_help)
|
||||
manpage_options = extract_options(manpage_text)
|
||||
|
||||
# print(f'Program options: {program_options}')
|
||||
# print(f'Manpage options: {manpage_options}')
|
||||
|
||||
# subset of options that are in the manpage but not in the program
|
||||
obsolete_options = manpage_options - program_options
|
||||
|
||||
# subset of options that are in the program but not in the manpage
|
||||
missing_options = program_options - manpage_options
|
||||
|
||||
exit_code = 0
|
||||
|
||||
if obsolete_options:
|
||||
print(termcolor.colored(f"{manpage_basename}: obsolete options:", "red"))
|
||||
for option in obsolete_options:
|
||||
print(f" --{option}")
|
||||
exit_code = 1
|
||||
|
||||
if missing_options:
|
||||
print(termcolor.colored(f"{manpage_basename}: missing options:", "red"))
|
||||
for option in missing_options:
|
||||
print(f" --{option}")
|
||||
exit_code = 1
|
||||
|
||||
if exit_code == 0:
|
||||
print(
|
||||
termcolor.colored(f"{manpage_basename}: OK", "green")
|
||||
+ f" (checked {len(program_options)} options)"
|
||||
)
|
||||
|
||||
sys.exit(exit_code)
|
@ -1,32 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
script_dir="$(cd $(dirname $0); pwd)"
|
||||
manpage_dir="$script_dir/../doc"
|
||||
check_manpage_script="$script_dir/check_manpage.py"
|
||||
|
||||
# function to check tool help vs manpage
|
||||
|
||||
function check_manpage() {
|
||||
local tool="./$1"
|
||||
local tool_help=$2
|
||||
local manpage="$manpage_dir/$1.md"
|
||||
|
||||
# check that the manpage exists
|
||||
if [ ! -f "$manpage" ]; then
|
||||
echo "ERROR: '$manpage' does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# check that the tool exists
|
||||
if [ ! -f "$tool" ]; then
|
||||
echo "ERROR: '$tool' does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
$check_manpage_script "$manpage" "$tool" "$tool_help"
|
||||
}
|
||||
|
||||
check_manpage mkdwarfs --long-help
|
||||
check_manpage dwarfsck --help
|
||||
check_manpage dwarfsextract --help
|
||||
# cannot easily check `dwarfs` at the moment
|
@ -19,38 +19,62 @@
|
||||
* along with dwarfs. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <dwarfs/tool/main_adapter.h>
|
||||
#include <dwarfs/tool/pager.h>
|
||||
#include <dwarfs/tool/render_manpage.h>
|
||||
#include <dwarfs_tool_main.h>
|
||||
#include <dwarfs_tool_manpage.h>
|
||||
|
||||
#include "test_helpers.h"
|
||||
|
||||
using namespace dwarfs;
|
||||
using namespace dwarfs::tool;
|
||||
using namespace std::string_literals;
|
||||
|
||||
namespace {
|
||||
|
||||
std::map<std::string, manpage::document> const docs = {
|
||||
{"mkdwarfs", manpage::get_mkdwarfs_manpage()},
|
||||
{"dwarfs", manpage::get_dwarfs_manpage()},
|
||||
{"dwarfsck", manpage::get_dwarfsck_manpage()},
|
||||
{"dwarfsextract", manpage::get_dwarfsextract_manpage()},
|
||||
struct tool_defs {
|
||||
manpage::document doc;
|
||||
main_adapter::main_fn_type main;
|
||||
std::string_view help_option;
|
||||
bool is_fuse;
|
||||
};
|
||||
|
||||
}
|
||||
std::map<std::string, tool_defs> const tools = {
|
||||
{"mkdwarfs", {manpage::get_mkdwarfs_manpage(), mkdwarfs_main, "-H", false}},
|
||||
{"dwarfs", {manpage::get_dwarfs_manpage(), dwarfs_main, "-h", true}},
|
||||
{"dwarfsck", {manpage::get_dwarfsck_manpage(), dwarfsck_main, "-h", false}},
|
||||
{"dwarfsextract",
|
||||
{manpage::get_dwarfsextract_manpage(), dwarfsextract_main, "-h", false}},
|
||||
};
|
||||
|
||||
std::array const coverage_tests{
|
||||
"mkdwarfs"s,
|
||||
"dwarfsck"s,
|
||||
"dwarfsextract"s,
|
||||
#ifndef DWARFS_TEST_RUNNING_ON_ASAN
|
||||
// FUSE driver is leaky, so we don't run this test under ASAN
|
||||
"dwarfs"s,
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
class manpage_render_test
|
||||
: public ::testing::TestWithParam<std::tuple<std::string, bool>> {};
|
||||
|
||||
TEST_P(manpage_render_test, basic) {
|
||||
auto [name, color] = GetParam();
|
||||
auto doc = docs.at(name);
|
||||
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);
|
||||
@ -68,6 +92,96 @@ INSTANTIATE_TEST_SUITE_P(
|
||||
"dwarfsextract"),
|
||||
::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<std::string, std::string>
|
||||
parse_options(std::string const& text, std::regex const& re, bool is_fuse) {
|
||||
std::map<std::string, std::string> 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<std::string> {};
|
||||
|
||||
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<std::string_view, 2> 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<std::string, std::filesystem::path> const programs = {
|
||||
|
Loading…
x
Reference in New Issue
Block a user