From c5340cfbc2edb6938ea9387be6cdd9a81cd26a43 Mon Sep 17 00:00:00 2001 From: Marcus Holland-Moritz Date: Thu, 28 Dec 2023 18:25:30 +0100 Subject: [PATCH] test: test command line tool usage / help --- src/dwarfs_main.cpp | 17 ++-- src/dwarfsck_main.cpp | 4 +- src/dwarfsextract_main.cpp | 5 +- src/mkdwarfs_main.cpp | 2 +- test/test_helpers.h | 1 + test/test_iolayer.cpp | 3 + test/tool_main_test.cpp | 159 +++++++++++++++++++++++++++++++++++++ 7 files changed, 181 insertions(+), 10 deletions(-) diff --git a/src/dwarfs_main.cpp b/src/dwarfs_main.cpp index 672eba9ed..891ea1b54 100644 --- a/src/dwarfs_main.cpp +++ b/src/dwarfs_main.cpp @@ -132,6 +132,7 @@ struct dwarfs_userdata { dwarfs_userdata(dwarfs_userdata const&) = delete; dwarfs_userdata& operator=(dwarfs_userdata const&) = delete; + bool is_help{false}; options opts; stream_logger lgr; filesystem_v2 fs; @@ -962,7 +963,7 @@ void usage(std::ostream& os, std::filesystem::path const& progname) { #if !DWARFS_FUSE_LOWLEVEL << "USING HIGH-LEVEL FUSE API\n\n" #endif - << "usage: " << progname.filename().string() + << "Usage: " << progname.filename().string() << " [options]\n\n" << "DWARFS options:\n" << " -o cachesize=SIZE set size of block cache (512M)\n" @@ -995,8 +996,6 @@ void usage(std::ostream& os, std::filesystem::path const& progname) { fuse_main(args.argc, args.argv, &fsops, nullptr); fuse_opt_free_args(&args); #endif - - ::exit(1); } int option_hdl(void* data, char const* arg, int key, @@ -1022,7 +1021,8 @@ int option_hdl(void* data, char const* arg, int key, case FUSE_OPT_KEY_OPT: if (::strncmp(arg, "-h", 2) == 0 || ::strncmp(arg, "--help", 6) == 0) { - usage(userdata.iol.err, opts.progname); + userdata.is_help = true; + return -1; } break; @@ -1265,7 +1265,8 @@ int dwarfs_main(int argc, sys_char** argv, iolayer const& iol) { struct fuse_cmdline_opts fuse_opts; if (fuse_parse_cmdline(&args, &fuse_opts) == -1 || !fuse_opts.mountpoint) { - usage(iol.err, opts.progname); + usage(iol.out, opts.progname); + return userdata.is_help ? 0 : 1; } if (fuse_opts.foreground) { @@ -1278,7 +1279,8 @@ int dwarfs_main(int argc, sys_char** argv, iolayer const& iol) { int mt, fg; if (fuse_parse_cmdline(&args, &mountpoint, &mt, &fg) == -1 || !mountpoint) { - usage(iol.err, opts.progname); + usage(iol.out, opts.progname); + return userdata.is_help ? 0 : 1; } if (fg) { @@ -1349,7 +1351,8 @@ int dwarfs_main(int argc, sys_char** argv, iolayer const& iol) { } if (!opts.seen_mountpoint) { - usage(iol.err, opts.progname); + usage(iol.out, opts.progname); + return 1; } LOG_PROXY(debug_logger_policy, userdata.lgr); diff --git a/src/dwarfsck_main.cpp b/src/dwarfsck_main.cpp index 5cf666ef7..27dc31eb3 100644 --- a/src/dwarfsck_main.cpp +++ b/src/dwarfsck_main.cpp @@ -113,8 +113,10 @@ int dwarfsck_main(int argc, sys_char** argv, iolayer const& iol) { return 1; } + auto constexpr usage = "Usage: dwarfsck [OPTIONS...]\n"; + if (vm.count("help") or !vm.count("input")) { - iol.out << tool_header("dwarfsck") << opts << "\n"; + iol.out << tool_header("dwarfsck") << usage << "\n" << opts << "\n"; return 0; } diff --git a/src/dwarfsextract_main.cpp b/src/dwarfsextract_main.cpp index 2694afeac..3520b6d13 100644 --- a/src/dwarfsextract_main.cpp +++ b/src/dwarfsextract_main.cpp @@ -103,9 +103,12 @@ int dwarfsextract_main(int argc, sys_char** argv, iolayer const& iol) { return 1; } + auto constexpr usage = "Usage: dwarfsextract [OPTIONS...]\n"; + if (vm.count("help") or !vm.count("input")) { - iol.err << tool_header("dwarfsextract") << "using " + iol.out << tool_header("dwarfsextract") << "using " << ::archive_version_string() << "\n\n" + << usage << "\n" << opts << "\n"; return 0; } diff --git a/src/mkdwarfs_main.cpp b/src/mkdwarfs_main.cpp index 491d98c11..c58f2ed5a 100644 --- a/src/mkdwarfs_main.cpp +++ b/src/mkdwarfs_main.cpp @@ -621,7 +621,7 @@ int mkdwarfs_main(int argc, sys_char** argv, iolayer const& iol) { return 1; } - auto const usage = "Usage: mkdwarfs [OPTIONS...]\n"; + auto constexpr usage = "Usage: mkdwarfs [OPTIONS...]\n"; if (vm.count("long-help")) { std::string_view constexpr block_data_hdr{"Block Data"}; diff --git a/test/test_helpers.h b/test/test_helpers.h index ce58b0bbb..e1fc618c1 100644 --- a/test/test_helpers.h +++ b/test/test_helpers.h @@ -174,6 +174,7 @@ class test_file_access : public file_access { class test_iolayer { public: + test_iolayer(); test_iolayer(std::shared_ptr os); test_iolayer(std::shared_ptr os, std::shared_ptr fa); diff --git a/test/test_iolayer.cpp b/test/test_iolayer.cpp index cce4a6ace..3a1d1a7fe 100644 --- a/test/test_iolayer.cpp +++ b/test/test_iolayer.cpp @@ -170,6 +170,9 @@ std::string test_terminal::colored(std::string text, termcolor color, return result; } +test_iolayer::test_iolayer() + : test_iolayer{os_access_mock::create_test_instance()} {} + test_iolayer::test_iolayer(std::shared_ptr os) : test_iolayer{std::move(os), create_file_access_generic()} {} diff --git a/test/tool_main_test.cpp b/test/tool_main_test.cpp index fb62b35e6..2e0a7b5ba 100644 --- a/test/tool_main_test.cpp +++ b/test/tool_main_test.cpp @@ -21,6 +21,7 @@ #include +#include #include #include @@ -42,8 +43,166 @@ namespace { auto test_dir = fs::path(TEST_DATA_DIR).make_preferred(); auto audio_data_dir = test_dir / "pcmaudio"; +class tool_main_test : public testing::Test { + public: + void SetUp() override { 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; +}; + } // namespace +class mkdwarfs_main_test : public tool_main_test { + public: + int run(std::vector args) { + args.insert(args.begin(), "mkdwarfs"); + return 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 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 dwarfsextract_main(args, iol->get()); + } +}; + +class dwarfs_main_test : public tool_main_test { + public: + int run(std::vector args) { + args.insert(args.begin(), "dwarfs"); + return dwarfs_main(args, iol->get()); + } +}; + +TEST_F(mkdwarfs_main_test, no_cmdline_args) { + auto exit_code = run({}); + EXPECT_EQ(exit_code, 0); + EXPECT_TRUE(err().empty()); + EXPECT_FALSE(out().empty()); + EXPECT_THAT(out(), ::testing::HasSubstr("Usage: mkdwarfs")); + EXPECT_THAT(out(), ::testing::HasSubstr("--help")); +} + +TEST_F(dwarfsck_main_test, no_cmdline_args) { + auto exit_code = run({}); + EXPECT_EQ(exit_code, 0); + EXPECT_TRUE(err().empty()); + EXPECT_FALSE(out().empty()); + EXPECT_THAT(out(), ::testing::HasSubstr("Usage: dwarfsck")); + EXPECT_THAT(out(), ::testing::HasSubstr("--help")); +} + +TEST_F(dwarfsextract_main_test, no_cmdline_args) { + auto exit_code = run({}); + EXPECT_EQ(exit_code, 0); + EXPECT_TRUE(err().empty()); + EXPECT_FALSE(out().empty()); + EXPECT_THAT(out(), ::testing::HasSubstr("Usage: dwarfsextract")); + EXPECT_THAT(out(), ::testing::HasSubstr("--help")); +} + +TEST_F(dwarfs_main_test, no_cmdline_args) { + auto exit_code = run({}); + EXPECT_EQ(exit_code, 1); // FUSE will exit with 1 + EXPECT_FALSE(out().empty()); + EXPECT_TRUE(err().empty()); + EXPECT_THAT(out(), ::testing::HasSubstr("Usage: dwarfs")); + // Cannot check for --help here because FUSE will print to stdout + // EXPECT_THAT(err(), ::testing::HasSubstr("--help")); +} + +TEST_F(mkdwarfs_main_test, invalid_cmdline_args) { + auto exit_code = run({"--some-invalid-option"}); + EXPECT_EQ(exit_code, 1); + EXPECT_FALSE(err().empty()); + EXPECT_TRUE(out().empty()); + EXPECT_THAT(err(), ::testing::HasSubstr( + "unrecognised option '--some-invalid-option'")); +} + +TEST_F(dwarfsck_main_test, invalid_cmdline_args) { + auto exit_code = run({"--some-invalid-option"}); + EXPECT_EQ(exit_code, 1); + EXPECT_FALSE(err().empty()); + EXPECT_TRUE(out().empty()); + EXPECT_THAT(err(), ::testing::HasSubstr( + "unrecognised option '--some-invalid-option'")); +} + +TEST_F(dwarfsextract_main_test, invalid_cmdline_args) { + auto exit_code = run({"--some-invalid-option"}); + EXPECT_EQ(exit_code, 1); + EXPECT_FALSE(err().empty()); + EXPECT_TRUE(out().empty()); + EXPECT_THAT(err(), ::testing::HasSubstr( + "unrecognised option '--some-invalid-option'")); +} + +TEST_F(mkdwarfs_main_test, cmdline_help_arg) { + auto exit_code = run({"--help"}); + EXPECT_EQ(exit_code, 0); + EXPECT_TRUE(err().empty()); + EXPECT_FALSE(out().empty()); + EXPECT_THAT(out(), ::testing::HasSubstr("Usage: mkdwarfs")); + EXPECT_THAT(out(), ::testing::HasSubstr("--help")); + EXPECT_THAT(out(), ::testing::HasSubstr("--long-help")); + // check that the detailed help is not shown + EXPECT_THAT(out(), ::testing::Not(::testing::HasSubstr("Advanced options:"))); + EXPECT_THAT(out(), + ::testing::Not(::testing::HasSubstr("Compression algorithms:"))); +} + +TEST_F(mkdwarfs_main_test, cmdline_long_help_arg) { + auto exit_code = run({"--long-help"}); + EXPECT_EQ(exit_code, 0); + EXPECT_TRUE(err().empty()); + EXPECT_FALSE(out().empty()); + EXPECT_THAT(out(), ::testing::HasSubstr("Usage: mkdwarfs")); + EXPECT_THAT(out(), ::testing::HasSubstr("Advanced options:")); + EXPECT_THAT(out(), ::testing::HasSubstr("Compression level defaults:")); + EXPECT_THAT(out(), ::testing::HasSubstr("Compression algorithms:")); + EXPECT_THAT(out(), ::testing::HasSubstr("Categories:")); +} + +TEST_F(dwarfsck_main_test, cmdline_help_arg) { + auto exit_code = run({"--help"}); + EXPECT_EQ(exit_code, 0); + EXPECT_TRUE(err().empty()); + EXPECT_FALSE(out().empty()); + EXPECT_THAT(out(), ::testing::HasSubstr("Usage: dwarfs")); +} + +TEST_F(dwarfsextract_main_test, cmdline_help_arg) { + auto exit_code = run({"--help"}); + EXPECT_EQ(exit_code, 0); + EXPECT_TRUE(err().empty()); + EXPECT_FALSE(out().empty()); + EXPECT_THAT(out(), ::testing::HasSubstr("Usage: dwarfs")); +} + +TEST_F(dwarfs_main_test, cmdline_help_arg) { + auto exit_code = run({"--help"}); + EXPECT_EQ(exit_code, 0); + EXPECT_TRUE(err().empty()); + EXPECT_FALSE(out().empty()); + EXPECT_THAT(out(), ::testing::HasSubstr("Usage: dwarfs")); +} + class categorizer_test : public testing::TestWithParam {}; TEST_P(categorizer_test, end_to_end) {