diff options
author | 2017-09-08 18:00:31 -0700 | |
---|---|---|
committer | 2017-09-14 13:40:27 -0700 | |
commit | a8bedc6ae3f6d610aa634445809835c26aaf16bc (patch) | |
tree | cbfdbeef473f3073d5b7172ddc5dbd78232a38ee | |
parent | ded398e5b6530c4ced5bd42f9b75ca2caa527239 (diff) |
lshal: add HelpCommand
Add *Command::usage() function for each Command and let
Lshal class to call them.
Suppress output from getopt_long and write our own
error message to customized error stream (for testing).
Test: lshal_test
Test: lshal --help
Change-Id: I8f5847c84a3e01af29fa85871479cab3baeb5312
-rw-r--r-- | cmds/lshal/Android.bp | 1 | ||||
-rw-r--r-- | cmds/lshal/Command.h | 4 | ||||
-rw-r--r-- | cmds/lshal/DebugCommand.cpp | 20 | ||||
-rw-r--r-- | cmds/lshal/DebugCommand.h | 5 | ||||
-rw-r--r-- | cmds/lshal/HelpCommand.cpp | 68 | ||||
-rw-r--r-- | cmds/lshal/HelpCommand.h | 45 | ||||
-rw-r--r-- | cmds/lshal/ListCommand.cpp | 55 | ||||
-rw-r--r-- | cmds/lshal/ListCommand.h | 5 | ||||
-rw-r--r-- | cmds/lshal/Lshal.cpp | 97 | ||||
-rw-r--r-- | cmds/lshal/Lshal.h | 5 | ||||
-rw-r--r-- | cmds/lshal/test.cpp | 68 | ||||
-rw-r--r-- | cmds/lshal/utils.h | 1 |
12 files changed, 284 insertions, 90 deletions
diff --git a/cmds/lshal/Android.bp b/cmds/lshal/Android.bp index 47814688a9..5a87505113 100644 --- a/cmds/lshal/Android.bp +++ b/cmds/lshal/Android.bp @@ -25,6 +25,7 @@ cc_library_shared { ], srcs: [ "DebugCommand.cpp", + "HelpCommand.cpp", "Lshal.cpp", "ListCommand.cpp", "PipeRelay.cpp", diff --git a/cmds/lshal/Command.h b/cmds/lshal/Command.h index b1efb97c3b..aff49752e3 100644 --- a/cmds/lshal/Command.h +++ b/cmds/lshal/Command.h @@ -31,7 +31,9 @@ public: virtual ~Command() = default; // Expect optind to be set by Lshal::main and points to the next argument // to process. - virtual Status main(const std::string &command, const Arg &arg) = 0; + virtual Status main(const Arg &arg) = 0; + + virtual void usage() const = 0; protected: Lshal& mLshal; diff --git a/cmds/lshal/DebugCommand.cpp b/cmds/lshal/DebugCommand.cpp index e657bcf4e6..d21764ce3e 100644 --- a/cmds/lshal/DebugCommand.cpp +++ b/cmds/lshal/DebugCommand.cpp @@ -21,9 +21,8 @@ namespace android { namespace lshal { -Status DebugCommand::parseArgs(const std::string &command, const Arg &arg) { +Status DebugCommand::parseArgs(const Arg &arg) { if (optind >= arg.argc) { - mLshal.usage(command); return USAGE; } mInterfaceName = arg.argv[optind]; @@ -34,8 +33,8 @@ Status DebugCommand::parseArgs(const std::string &command, const Arg &arg) { return OK; } -Status DebugCommand::main(const std::string &command, const Arg &arg) { - Status status = parseArgs(command, arg); +Status DebugCommand::main(const Arg &arg) { + Status status = parseArgs(arg); if (status != OK) { return status; } @@ -46,6 +45,19 @@ Status DebugCommand::main(const std::string &command, const Arg &arg) { mLshal.err()); } +void DebugCommand::usage() const { + + static const std::string debug = + "debug:\n" + " lshal debug <interface> [options [options [...]]] \n" + " Print debug information of a specified interface.\n" + " <inteface>: Format is `android.hardware.foo@1.0::IFoo/default`.\n" + " If instance name is missing `default` is used.\n" + " options: space separated options to IBase::debug.\n"; + + mLshal.err() << debug; +} + } // namespace lshal } // namespace android diff --git a/cmds/lshal/DebugCommand.h b/cmds/lshal/DebugCommand.h index afa5e9788e..6b70713228 100644 --- a/cmds/lshal/DebugCommand.h +++ b/cmds/lshal/DebugCommand.h @@ -33,9 +33,10 @@ class DebugCommand : public Command { public: DebugCommand(Lshal &lshal) : Command(lshal) {} ~DebugCommand() = default; - Status main(const std::string &command, const Arg &arg) override; + Status main(const Arg &arg) override; + void usage() const override; private: - Status parseArgs(const std::string &command, const Arg &arg); + Status parseArgs(const Arg &arg); std::string mInterfaceName; std::vector<std::string> mOptions; diff --git a/cmds/lshal/HelpCommand.cpp b/cmds/lshal/HelpCommand.cpp new file mode 100644 index 0000000000..b393f052be --- /dev/null +++ b/cmds/lshal/HelpCommand.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "HelpCommand.h" + +#include "Lshal.h" + +namespace android { +namespace lshal { + +Status HelpCommand::main(const Arg &arg) { + if (optind >= arg.argc) { + // `lshal help` prints global usage. + mLshal.usage(); + return OK; + } + (void)usageOfCommand(arg.argv[optind]); + return OK; +} + +Status HelpCommand::usageOfCommand(const std::string& c) const { + if (c.empty()) { + mLshal.usage(); + return USAGE; + } + auto command = mLshal.selectCommand(c); + if (command == nullptr) { + // from HelpCommand::main, `lshal help unknown` + mLshal.usage(); + return USAGE; + } + + command->usage(); + return USAGE; + +} + +void HelpCommand::usage() const { + static const std::string help = + "help:\n" + " lshal -h\n" + " lshal --help\n" + " lshal help\n" + " Print this help message\n" + " lshal help list\n" + " Print help message for list\n" + " lshal help debug\n" + " Print help message for debug\n"; + + mLshal.err() << help; +} + +} // namespace lshal +} // namespace android + diff --git a/cmds/lshal/HelpCommand.h b/cmds/lshal/HelpCommand.h new file mode 100644 index 0000000000..3cc0d8064a --- /dev/null +++ b/cmds/lshal/HelpCommand.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FRAMEWORK_NATIVE_CMDS_LSHAL_HELP_COMMAND_H_ +#define FRAMEWORK_NATIVE_CMDS_LSHAL_HELP_COMMAND_H_ + +#include <string> + +#include <android-base/macros.h> + +#include "Command.h" +#include "utils.h" + +namespace android { +namespace lshal { + +class Lshal; + +class HelpCommand : public Command { +public: + HelpCommand(Lshal &lshal) : Command(lshal) {} + ~HelpCommand() = default; + Status main(const Arg &arg) override; + void usage() const override; + Status usageOfCommand(const std::string& c) const; +}; + + +} // namespace lshal +} // namespace android + +#endif // FRAMEWORK_NATIVE_CMDS_LSHAL_HELP_COMMAND_H_ diff --git a/cmds/lshal/ListCommand.cpp b/cmds/lshal/ListCommand.cpp index ffb442460c..71ac25bdba 100644 --- a/cmds/lshal/ListCommand.cpp +++ b/cmds/lshal/ListCommand.cpp @@ -604,7 +604,7 @@ Status ListCommand::fetch() { return status; } -Status ListCommand::parseArgs(const std::string &command, const Arg &arg) { +Status ListCommand::parseArgs(const Arg &arg) { static struct option longOptions[] = { // long options with short alternatives {"help", no_argument, 0, 'h' }, @@ -628,6 +628,9 @@ Status ListCommand::parseArgs(const std::string &command, const Arg &arg) { std::vector<TableColumnType> selectedColumns; bool enableCmdlines = false; + // suppress output to std::err for unknown options + opterr = 0; + int optionIndex; int c; // Lshal::parseArgs has set optind to the next option to parse @@ -646,7 +649,6 @@ Status ListCommand::parseArgs(const std::string &command, const Arg &arg) { mSortColumn = TableEntry::sortByServerPid; } else { err() << "Unrecognized sorting column: " << optarg << std::endl; - mLshal.usage(command); return USAGE; } break; @@ -697,22 +699,22 @@ Status ListCommand::parseArgs(const std::string &command, const Arg &arg) { mNeat = true; break; } - case 'h': // falls through + case 'h': { + return USAGE; + } default: // see unrecognized options - mLshal.usage(command); + err() << "unrecognized option `" << arg.argv[optind - 1] << "'" << std::endl; return USAGE; } } if (optind < arg.argc) { // see non option - err() << "Unrecognized option `" << arg.argv[optind] << "`" << std::endl; - mLshal.usage(command); + err() << "unrecognized option `" << arg.argv[optind] << "'" << std::endl; return USAGE; } if (mNeat && mEmitDebugInfo) { err() << "Error: --neat should not be used with --debug." << std::endl; - mLshal.usage(command); return USAGE; } @@ -739,8 +741,8 @@ Status ListCommand::parseArgs(const std::string &command, const Arg &arg) { return OK; } -Status ListCommand::main(const std::string &command, const Arg &arg) { - Status status = parseArgs(command, arg); +Status ListCommand::main(const Arg &arg) { + Status status = parseArgs(arg); if (status != OK) { return status; } @@ -750,6 +752,41 @@ Status ListCommand::main(const std::string &command, const Arg &arg) { return status; } +void ListCommand::usage() const { + + static const std::string list = + "list:\n" + " lshal\n" + " lshal list\n" + " List all hals with default ordering and columns (`lshal list -iepc`)\n" + " lshal list [-h|--help]\n" + " -h, --help: Print help message for list (`lshal help list`)\n" + " lshal [list] [--interface|-i] [--transport|-t] [-r|--arch] [-e|--threads]\n" + " [--pid|-p] [--address|-a] [--clients|-c] [--cmdline|-m]\n" + " [--sort={interface|i|pid|p}] [--init-vintf[=<output file>]]\n" + " [--debug|-d[=<output file>]] [--neat]\n" + " -i, --interface: print the interface name column\n" + " -n, --instance: print the instance name column\n" + " -t, --transport: print the transport mode column\n" + " -r, --arch: print if the HAL is in 64-bit or 32-bit\n" + " -e, --threads: print currently used/available threads\n" + " (note, available threads created lazily)\n" + " -p, --pid: print the server PID, or server cmdline if -m is set\n" + " -a, --address: print the server object address column\n" + " -c, --clients: print the client PIDs, or client cmdlines if -m is set\n" + " -m, --cmdline: print cmdline instead of PIDs\n" + " -d[=<output file>], --debug[=<output file>]: emit debug info from \n" + " IBase::debug with empty options. Cannot be used with --neat.\n" + " --sort=i, --sort=interface: sort by interface name\n" + " --sort=p, --sort=pid: sort by server pid\n" + " --neat: output is machine parsable (no explanatory text)\n" + " Cannot be used with --debug.\n" + " --init-vintf[=<output file>]: form a skeleton HAL manifest to specified\n" + " file, or stdout if no file specified.\n"; + + err() << list; +} + } // namespace lshal } // namespace android diff --git a/cmds/lshal/ListCommand.h b/cmds/lshal/ListCommand.h index 8d25d94b77..6defb0aef0 100644 --- a/cmds/lshal/ListCommand.h +++ b/cmds/lshal/ListCommand.h @@ -47,9 +47,10 @@ class ListCommand : public Command { public: ListCommand(Lshal &lshal) : Command(lshal) {} virtual ~ListCommand() = default; - Status main(const std::string &command, const Arg &arg) override; + Status main(const Arg &arg) override; + void usage() const override; protected: - Status parseArgs(const std::string &command, const Arg &arg); + Status parseArgs(const Arg &arg); Status fetch(); void postprocess(); Status dump(); diff --git a/cmds/lshal/Lshal.cpp b/cmds/lshal/Lshal.cpp index 9c5c234f16..a08a02ce1a 100644 --- a/cmds/lshal/Lshal.cpp +++ b/cmds/lshal/Lshal.cpp @@ -48,7 +48,7 @@ Lshal::Lshal(std::ostream &out, std::ostream &err, } -void Lshal::usage(const std::string &command) const { +void Lshal::usage() { static const std::string helpSummary = "lshal: List and debug HALs.\n" "\n" @@ -59,65 +59,12 @@ void Lshal::usage(const std::string &command) const { "\n" "If no command is specified, `list` is the default.\n"; - static const std::string list = - "list:\n" - " lshal\n" - " lshal list\n" - " List all hals with default ordering and columns (`lshal list -iepc`)\n" - " lshal list [-h|--help]\n" - " -h, --help: Print help message for list (`lshal help list`)\n" - " lshal [list] [--interface|-i] [--transport|-t] [-r|--arch] [-e|--threads]\n" - " [--pid|-p] [--address|-a] [--clients|-c] [--cmdline|-m]\n" - " [--sort={interface|i|pid|p}] [--init-vintf[=<output file>]]\n" - " [--debug|-d[=<output file>]] [--neat]\n" - " -i, --interface: print the interface name column\n" - " -n, --instance: print the instance name column\n" - " -t, --transport: print the transport mode column\n" - " -r, --arch: print if the HAL is in 64-bit or 32-bit\n" - " -e, --threads: print currently used/available threads\n" - " (note, available threads created lazily)\n" - " -p, --pid: print the server PID, or server cmdline if -m is set\n" - " -a, --address: print the server object address column\n" - " -c, --clients: print the client PIDs, or client cmdlines if -m is set\n" - " -m, --cmdline: print cmdline instead of PIDs\n" - " -d[=<output file>], --debug[=<output file>]: emit debug info from \n" - " IBase::debug with empty options. Cannot be used with --neat.\n" - " --sort=i, --sort=interface: sort by interface name\n" - " --sort=p, --sort=pid: sort by server pid\n" - " --neat: output is machine parsable (no explanatory text)\n" - " Cannot be used with --debug.\n" - " --init-vintf[=<output file>]: form a skeleton HAL manifest to specified\n" - " file, or stdout if no file specified.\n"; - - static const std::string debug = - "debug:\n" - " lshal debug <interface> [options [options [...]]] \n" - " Print debug information of a specified interface.\n" - " <inteface>: Format is `android.hardware.foo@1.0::IFoo/default`.\n" - " If instance name is missing `default` is used.\n" - " options: space separated options to IBase::debug.\n"; - - static const std::string help = - "help:\n" - " lshal -h\n" - " lshal --help\n" - " lshal help\n" - " Print this help message\n" - " lshal help list\n" - " Print help message for list\n" - " lshal help debug\n" - " Print help message for debug\n"; - - if (command == "list") { - err() << list; - return; - } - if (command == "debug") { - err() << debug; - return; - } - - err() << helpSummary << "\n" << list << "\n" << debug << "\n" << help; + err() << helpSummary << "\n"; + selectCommand("list")->usage(); + err() << "\n"; + selectCommand("debug")->usage(); + err() << "\n"; + selectCommand("help")->usage(); } // A unique_ptr type using a custom deleter function. @@ -206,8 +153,7 @@ Status Lshal::parseArgs(const Arg &arg) { return OK; } - err() << arg.argv[0] << ": unrecognized option `" << arg.argv[optind] << "`" << std::endl; - usage(); + err() << arg.argv[0] << ": unrecognized option `" << arg.argv[optind] << "'" << std::endl; return USAGE; } @@ -218,6 +164,10 @@ void signalHandler(int sig) { } } +std::unique_ptr<HelpCommand> Lshal::selectHelpCommand() { + return std::make_unique<HelpCommand>(*this); +} + std::unique_ptr<Command> Lshal::selectCommand(const std::string& command) { // Default command is list if (command == "list" || command == "") { @@ -226,6 +176,9 @@ std::unique_ptr<Command> Lshal::selectCommand(const std::string& command) { if (command == "debug") { return std::make_unique<DebugCommand>(*this); } + if (command == "help") { + return selectHelpCommand(); + } return nullptr; } @@ -235,18 +188,24 @@ Status Lshal::main(const Arg &arg) { Status status = parseArgs(arg); if (status != OK) { + usage(); return status; } - if (mCommand == "help") { - usage(optind < arg.argc ? arg.argv[optind] : ""); + auto c = selectCommand(mCommand); + if (c == nullptr) { + // unknown command, print global usage + usage(); return USAGE; } - auto c = selectCommand(mCommand); - if (c != nullptr) { - return c->main(mCommand, arg); + status = c->main(arg); + if (status == USAGE) { + // bad options. Run `lshal help ${mCommand}` instead. + // For example, `lshal --unknown-option` becomes `lshal help` (prints global help) + // and `lshal list --unknown-option` becomes `lshal help list` + return selectHelpCommand()->usageOfCommand(mCommand); } - usage(); - return USAGE; + + return status; } NullableOStream<std::ostream> Lshal::err() const { diff --git a/cmds/lshal/Lshal.h b/cmds/lshal/Lshal.h index bfd6a404f8..89b38db7df 100644 --- a/cmds/lshal/Lshal.h +++ b/cmds/lshal/Lshal.h @@ -25,6 +25,7 @@ #include <utils/StrongPointer.h> #include "Command.h" +#include "HelpCommand.h" #include "NullableOStream.h" #include "utils.h" @@ -39,7 +40,8 @@ public: sp<hidl::manager::V1_0::IServiceManager> serviceManager, sp<hidl::manager::V1_0::IServiceManager> passthroughManager); Status main(const Arg &arg); - void usage(const std::string &command = "") const; + // global usage + void usage(); virtual NullableOStream<std::ostream> err() const; virtual NullableOStream<std::ostream> out() const; const sp<hidl::manager::V1_0::IServiceManager> &serviceManager() const; @@ -56,6 +58,7 @@ public: private: Status parseArgs(const Arg &arg); + std::unique_ptr<HelpCommand> selectHelpCommand(); std::string mCommand; Arg mCmdArgs; diff --git a/cmds/lshal/test.cpp b/cmds/lshal/test.cpp index 44b196e11f..06b68199cd 100644 --- a/cmds/lshal/test.cpp +++ b/cmds/lshal/test.cpp @@ -183,8 +183,8 @@ class MockListCommand : public ListCommand { public: MockListCommand(Lshal* lshal) : ListCommand(*lshal) {} - Status parseArgs(const Arg& arg) { return ListCommand::parseArgs("", arg); } - Status main(const Arg& arg) { return ListCommand::main("", arg); } + Status parseArgs(const Arg& arg) { return ListCommand::parseArgs(arg); } + Status main(const Arg& arg) { return ListCommand::main(arg); } void forEachTable(const std::function<void(const Table &)> &f) const { return ListCommand::forEachTable(f); } @@ -528,6 +528,70 @@ TEST_F(ListTest, DumpNeat) { EXPECT_EQ(expected, out.str()); EXPECT_EQ("", err.str()); } + +class HelpTest : public ::testing::Test { +public: + void SetUp() override { + lshal = std::make_unique<Lshal>(out, err, new MockServiceManager() /* serviceManager */, + new MockServiceManager() /* passthruManager */); + } + + std::stringstream err; + std::stringstream out; + std::unique_ptr<Lshal> lshal; +}; + +TEST_F(HelpTest, GlobalUsage) { + (void)callMain(lshal, {"lshal", "--help"}); // ignore return + std::string errStr = err.str(); + EXPECT_THAT(errStr, ContainsRegex("(^|\n)commands:($|\n)")) + << "`lshal --help` does not contain global usage"; + EXPECT_THAT(errStr, ContainsRegex("(^|\n)list:($|\n)")) + << "`lshal --help` does not contain usage for 'list' command"; + EXPECT_THAT(errStr, ContainsRegex("(^|\n)debug:($|\n)")) + << "`lshal --help` does not contain usage for 'debug' command"; + EXPECT_THAT(errStr, ContainsRegex("(^|\n)help:($|\n)")) + << "`lshal --help` does not contain usage for 'help' command"; + + err.str(""); + (void)callMain(lshal, {"lshal", "help"}); // ignore return + EXPECT_EQ(errStr, err.str()) << "`lshal help` should have the same output as `lshal --help`"; + + err.str(""); + EXPECT_NE(0u, callMain(lshal, {"lshal", "--unknown-option"})); + EXPECT_THAT(err.str(), ContainsRegex("unrecognized option")); + EXPECT_THAT(err.str(), EndsWith(errStr)) + << "`lshal --unknown-option` should have the same output as `lshal --help`"; + EXPECT_EQ("", out.str()); +} + +TEST_F(HelpTest, UnknownOptionList1) { + (void)callMain(lshal, {"lshal", "help", "list"}); + EXPECT_THAT(err.str(), ContainsRegex("(^|\n)list:($|\n)")) + << "`lshal help list` does not contain usage for 'list' command"; +} + +TEST_F(HelpTest, UnknownOptionList2) { + EXPECT_NE(0u, callMain(lshal, {"lshal", "list", "--unknown-option"})); + EXPECT_THAT(err.str(), ContainsRegex("unrecognized option")); + EXPECT_THAT(err.str(), ContainsRegex("(^|\n)list:($|\n)")) + << "`lshal list --unknown-option` does not contain usage for 'list' command"; + EXPECT_EQ("", out.str()); +} + +TEST_F(HelpTest, UnknownOptionHelp1) { + (void)callMain(lshal, {"lshal", "help", "help"}); + EXPECT_THAT(err.str(), ContainsRegex("(^|\n)help:($|\n)")) + << "`lshal help help` does not contain usage for 'help' command"; +} + +TEST_F(HelpTest, UnknownOptionHelp2) { + (void)callMain(lshal, {"lshal", "help", "--unknown-option"}); + EXPECT_THAT(err.str(), ContainsRegex("(^|\n)help:($|\n)")) + << "`lshal help --unknown-option` does not contain usage for 'help' command"; + EXPECT_EQ("", out.str()); +} + } // namespace lshal } // namespace android diff --git a/cmds/lshal/utils.h b/cmds/lshal/utils.h index 45b922cc03..1f680d599f 100644 --- a/cmds/lshal/utils.h +++ b/cmds/lshal/utils.h @@ -29,6 +29,7 @@ namespace lshal { enum : unsigned int { OK = 0, + // Return to Lshal::main to print help info. USAGE = 1 << 0, NO_BINDERIZED_MANAGER = 1 << 1, NO_PASSTHROUGH_MANAGER = 1 << 2, |