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
diff --git a/cmds/lshal/Android.bp b/cmds/lshal/Android.bp
index 4781468..5a87505 100644
--- a/cmds/lshal/Android.bp
+++ b/cmds/lshal/Android.bp
@@ -25,6 +25,7 @@
],
srcs: [
"DebugCommand.cpp",
+ "HelpCommand.cpp",
"Lshal.cpp",
"ListCommand.cpp",
"PipeRelay.cpp",
diff --git a/cmds/lshal/Command.h b/cmds/lshal/Command.h
index b1efb97..aff4975 100644
--- a/cmds/lshal/Command.h
+++ b/cmds/lshal/Command.h
@@ -31,7 +31,9 @@
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 e657bcf..d21764c 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 @@
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 @@
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 afa5e97..6b70713 100644
--- a/cmds/lshal/DebugCommand.h
+++ b/cmds/lshal/DebugCommand.h
@@ -33,9 +33,10 @@
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 0000000..b393f05
--- /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 0000000..3cc0d80
--- /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 ffb4424..71ac25b 100644
--- a/cmds/lshal/ListCommand.cpp
+++ b/cmds/lshal/ListCommand.cpp
@@ -604,7 +604,7 @@
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 @@
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 @@
mSortColumn = TableEntry::sortByServerPid;
} else {
err() << "Unrecognized sorting column: " << optarg << std::endl;
- mLshal.usage(command);
return USAGE;
}
break;
@@ -697,22 +699,22 @@
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 @@
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 @@
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 8d25d94..6defb0a 100644
--- a/cmds/lshal/ListCommand.h
+++ b/cmds/lshal/ListCommand.h
@@ -47,9 +47,10 @@
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 9c5c234..a08a02c 100644
--- a/cmds/lshal/Lshal.cpp
+++ b/cmds/lshal/Lshal.cpp
@@ -48,7 +48,7 @@
}
-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 @@
"\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 @@
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 @@
}
}
+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 @@
if (command == "debug") {
return std::make_unique<DebugCommand>(*this);
}
+ if (command == "help") {
+ return selectHelpCommand();
+ }
return nullptr;
}
@@ -235,18 +188,24 @@
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 bfd6a40..89b38db 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 @@
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 @@
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 44b196e..06b6819 100644
--- a/cmds/lshal/test.cpp
+++ b/cmds/lshal/test.cpp
@@ -183,8 +183,8 @@
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 @@
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 45b922c..1f680d5 100644
--- a/cmds/lshal/utils.h
+++ b/cmds/lshal/utils.h
@@ -29,6 +29,7 @@
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,