Loading cmds/lshal/ListCommand.cpp +80 −25 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ #include <getopt.h> #include <algorithm> #include <fstream> #include <functional> #include <iomanip> Loading @@ -27,6 +28,7 @@ #include <sstream> #include <android-base/file.h> #include <android-base/logging.h> #include <android-base/parseint.h> #include <android/hidl/manager/1.0/IServiceManager.h> #include <hidl-hash/Hash.h> Loading Loading @@ -220,16 +222,37 @@ const PidInfo* ListCommand::getPidInfoCached(pid_t serverPid) { return &pair.first->second; } // Must process hwbinder services first, then passthrough services. bool ListCommand::shouldReportHalType(const HalType &type) const { return (std::find(mListTypes.begin(), mListTypes.end(), type) != mListTypes.end()); } void ListCommand::forEachTable(const std::function<void(Table &)> &f) { f(mServicesTable); f(mPassthroughRefTable); f(mImplementationsTable); for (const auto& type : mListTypes) { switch (type) { case HalType::BINDERIZED_SERVICES: f(mServicesTable); break; case HalType::PASSTHROUGH_CLIENTS: f(mPassthroughRefTable); break; case HalType::PASSTHROUGH_LIBRARIES: f(mImplementationsTable); break; default: LOG(FATAL) << __func__ << "Unknown HAL type."; } } } void ListCommand::forEachTable(const std::function<void(const Table &)> &f) const { f(mServicesTable); f(mPassthroughRefTable); f(mImplementationsTable); for (const auto& type : mListTypes) { switch (type) { case HalType::BINDERIZED_SERVICES: f(mServicesTable); break; case HalType::PASSTHROUGH_CLIENTS: f(mPassthroughRefTable); break; case HalType::PASSTHROUGH_LIBRARIES: f(mImplementationsTable); break; default: LOG(FATAL) << __func__ << "Unknown HAL type."; } } } void ListCommand::postprocess() { Loading Loading @@ -498,6 +521,8 @@ void ListCommand::putEntry(TableEntrySource source, TableEntry &&entry) { } Status ListCommand::fetchAllLibraries(const sp<IServiceManager> &manager) { if (!shouldReportHalType(HalType::PASSTHROUGH_LIBRARIES)) { return OK; } using namespace ::android::hardware; using namespace ::android::hidl::manager::V1_0; using namespace ::android::hidl::base::V1_0; Loading Loading @@ -526,6 +551,8 @@ Status ListCommand::fetchAllLibraries(const sp<IServiceManager> &manager) { } Status ListCommand::fetchPassthrough(const sp<IServiceManager> &manager) { if (!shouldReportHalType(HalType::PASSTHROUGH_CLIENTS)) { return OK; } using namespace ::android::hardware; using namespace ::android::hardware::details; using namespace ::android::hidl::manager::V1_0; Loading Loading @@ -555,8 +582,9 @@ Status ListCommand::fetchPassthrough(const sp<IServiceManager> &manager) { } Status ListCommand::fetchBinderized(const sp<IServiceManager> &manager) { const std::string mode = "hwbinder"; if (!shouldReportHalType(HalType::BINDERIZED_SERVICES)) { return OK; } const std::string mode = "hwbinder"; hidl_vec<hidl_string> fqInstanceNames; // copying out for timeoutIPC auto listRet = timeoutIPC(manager, &IServiceManager::list, [&] (const auto &names) { Loading Loading @@ -790,6 +818,42 @@ void ListCommand::registerAllOptions() { thiz->mNeat = true; return OK; }, "output is machine parsable (no explanatory text).\nCannot be used with --debug."}); mOptions.push_back({'\0', "types", required_argument, v++, [](ListCommand* thiz, const char* arg) { if (!arg) { return USAGE; } static const std::map<std::string, HalType> kHalTypeMap { {"binderized", HalType::BINDERIZED_SERVICES}, {"b", HalType::BINDERIZED_SERVICES}, {"passthrough_clients", HalType::PASSTHROUGH_CLIENTS}, {"c", HalType::PASSTHROUGH_CLIENTS}, {"passthrough_libs", HalType::PASSTHROUGH_LIBRARIES}, {"l", HalType::PASSTHROUGH_LIBRARIES} }; std::vector<std::string> halTypesArgs = split(std::string(arg), ','); for (const auto& halTypeArg : halTypesArgs) { if (halTypeArg.empty()) continue; const auto& halTypeIter = kHalTypeMap.find(halTypeArg); if (halTypeIter == kHalTypeMap.end()) { thiz->err() << "Unrecognized HAL type: " << halTypeArg << std::endl; return USAGE; } // Append unique (non-repeated) HAL types to the reporting list HalType halType = halTypeIter->second; if (std::find(thiz->mListTypes.begin(), thiz->mListTypes.end(), halType) == thiz->mListTypes.end()) { thiz->mListTypes.push_back(halType); } } if (thiz->mListTypes.empty()) { return USAGE; } return OK; }, "comma-separated list of one or more HAL types.\nThe output is restricted to the selected " "association(s). Valid options\nare: (b|binderized), (c|passthrough_clients), and (l|" "passthrough_libs).\nBy default, lists all available HALs."}); } // Create 'longopts' argument to getopt_long. Caller is responsible for maintaining Loading Loading @@ -828,6 +892,7 @@ static std::string getShortOptions(const ListCommand::RegisteredOptions& options } Status ListCommand::parseArgs(const Arg &arg) { mListTypes.clear(); if (mOptions.empty()) { registerAllOptions(); Loading Loading @@ -900,6 +965,12 @@ Status ListCommand::parseArgs(const Arg &arg) { } } // By default, list all HAL types if (mListTypes.empty()) { mListTypes = {HalType::BINDERIZED_SERVICES, HalType::PASSTHROUGH_CLIENTS, HalType::PASSTHROUGH_LIBRARIES}; } forEachTable([this] (Table& table) { table.setSelectedColumns(this->mSelectedColumns); }); Loading @@ -918,22 +989,6 @@ Status ListCommand::main(const Arg &arg) { return status; } static std::vector<std::string> splitString(const std::string &s, char c) { std::vector<std::string> components; size_t startPos = 0; size_t matchPos; while ((matchPos = s.find(c, startPos)) != std::string::npos) { components.push_back(s.substr(startPos, matchPos - startPos)); startPos = matchPos + 1; } if (startPos <= s.length()) { components.push_back(s.substr(startPos)); } return components; } const std::string& ListCommand::RegisteredOption::getHelpMessageForArgument() const { static const std::string empty{}; static const std::string optional{"[=<arg>]"}; Loading Loading @@ -969,7 +1024,7 @@ void ListCommand::usage() const { if (!e.longOption.empty()) err() << "--" << e.longOption << e.getHelpMessageForArgument(); err() << ": "; std::vector<std::string> lines = splitString(e.help, '\n'); std::vector<std::string> lines = split(e.help, '\n'); for (const auto& line : lines) { if (&line != &lines.front()) err() << " "; Loading cmds/lshal/ListCommand.h +13 −0 Original line number Diff line number Diff line Loading @@ -46,6 +46,12 @@ struct PidInfo { uint32_t threadCount; // number of threads total }; enum class HalType { BINDERIZED_SERVICES = 0, PASSTHROUGH_CLIENTS, PASSTHROUGH_LIBRARIES }; class ListCommand : public Command { public: ListCommand(Lshal &lshal) : Command(lshal) {} Loading Loading @@ -128,6 +134,9 @@ protected: bool addEntryWithInstance(const TableEntry &entry, vintf::HalManifest *manifest) const; bool addEntryWithoutInstance(const TableEntry &entry, const vintf::HalManifest *manifest) const; // Helper function. Whether to list entries corresponding to a given HAL type. bool shouldReportHalType(const HalType &type) const; Table mServicesTable{}; Table mPassthroughRefTable{}; Table mImplementationsTable{}; Loading @@ -144,6 +153,10 @@ protected: // If true, explanatory text are not emitted. bool mNeat = false; // Type(s) of HAL associations to list. By default, report all. std::vector<HalType> mListTypes{HalType::BINDERIZED_SERVICES, HalType::PASSTHROUGH_CLIENTS, HalType::PASSTHROUGH_LIBRARIES}; // If an entry does not exist, need to ask /proc/{pid}/cmdline to get it. // If an entry exist but is an empty string, process might have died. // If an entry exist and not empty, it contains the cached content of /proc/{pid}/cmdline. Loading cmds/lshal/test.cpp +84 −0 Original line number Diff line number Diff line Loading @@ -567,6 +567,90 @@ TEST_F(ListTest, DumpNeat) { EXPECT_EQ("", err.str()); } TEST_F(ListTest, DumpSingleHalType) { const std::string expected = "[fake description 0]\n" "Interface Transport Arch Thread Use Server PTR Clients\n" "a.h.foo1@1.0::IFoo/1 hwbinder 64 11/21 1 0000000000002711 2 4\n" "a.h.foo2@2.0::IFoo/2 hwbinder 64 12/22 2 0000000000002712 3 5\n" "\n"; optind = 1; // mimic Lshal::parseArg() EXPECT_EQ(0u, mockList->main(createArg({"lshal", "-itrepac", "--types=binderized"}))); EXPECT_EQ(expected, out.str()); EXPECT_EQ("", err.str()); } TEST_F(ListTest, DumpReorderedHalTypes) { const std::string expected = "[fake description 0]\n" "Interface Transport Arch Thread Use Server PTR Clients\n" "a.h.foo3@3.0::IFoo/3 passthrough 32 N/A N/A N/A 4 6\n" "a.h.foo4@4.0::IFoo/4 passthrough 32 N/A N/A N/A 5 7\n" "\n" "[fake description 1]\n" "Interface Transport Arch Thread Use Server PTR Clients\n" "a.h.foo5@5.0::IFoo/5 passthrough 32 N/A N/A N/A 6 8\n" "a.h.foo6@6.0::IFoo/6 passthrough 32 N/A N/A N/A 7 9\n" "\n" "[fake description 2]\n" "Interface Transport Arch Thread Use Server PTR Clients\n" "a.h.foo1@1.0::IFoo/1 hwbinder 64 11/21 1 0000000000002711 2 4\n" "a.h.foo2@2.0::IFoo/2 hwbinder 64 12/22 2 0000000000002712 3 5\n" "\n"; optind = 1; // mimic Lshal::parseArg() EXPECT_EQ(0u, mockList->main(createArg({"lshal", "-itrepac", "--types=passthrough_clients", "--types=passthrough_libs", "--types=binderized"}))); EXPECT_EQ(expected, out.str()); EXPECT_EQ("", err.str()); } TEST_F(ListTest, DumpAbbreviatedHalTypes) { const std::string expected = "[fake description 0]\n" "Interface Transport Arch Thread Use Server PTR Clients\n" "a.h.foo3@3.0::IFoo/3 passthrough 32 N/A N/A N/A 4 6\n" "a.h.foo4@4.0::IFoo/4 passthrough 32 N/A N/A N/A 5 7\n" "\n" "[fake description 1]\n" "Interface Transport Arch Thread Use Server PTR Clients\n" "a.h.foo5@5.0::IFoo/5 passthrough 32 N/A N/A N/A 6 8\n" "a.h.foo6@6.0::IFoo/6 passthrough 32 N/A N/A N/A 7 9\n" "\n"; optind = 1; // mimic Lshal::parseArg() EXPECT_EQ(0u, mockList->main(createArg({"lshal", "-itrepac", "--types=c,l"}))); EXPECT_EQ(expected, out.str()); EXPECT_EQ("", err.str()); } TEST_F(ListTest, DumpEmptyAndDuplicateHalTypes) { const std::string expected = "[fake description 0]\n" "Interface Transport Arch Thread Use Server PTR Clients\n" "a.h.foo3@3.0::IFoo/3 passthrough 32 N/A N/A N/A 4 6\n" "a.h.foo4@4.0::IFoo/4 passthrough 32 N/A N/A N/A 5 7\n" "\n" "[fake description 1]\n" "Interface Transport Arch Thread Use Server PTR Clients\n" "a.h.foo5@5.0::IFoo/5 passthrough 32 N/A N/A N/A 6 8\n" "a.h.foo6@6.0::IFoo/6 passthrough 32 N/A N/A N/A 7 9\n" "\n"; optind = 1; // mimic Lshal::parseArg() EXPECT_EQ(0u, mockList->main(createArg({"lshal", "-itrepac", "--types=c,l,,,l,l,c,", "--types=passthrough_libs,passthrough_clients"}))); EXPECT_EQ(expected, out.str()); EXPECT_EQ("", err.str()); } TEST_F(ListTest, UnknownHalType) { optind = 1; // mimic Lshal::parseArg() EXPECT_EQ(1u, mockList->main(createArg({"lshal", "-itrepac", "--types=c,a"}))); EXPECT_THAT(err.str(), HasSubstr("Unrecognized HAL type: a")); } class HelpTest : public ::testing::Test { public: void SetUp() override { Loading Loading
cmds/lshal/ListCommand.cpp +80 −25 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ #include <getopt.h> #include <algorithm> #include <fstream> #include <functional> #include <iomanip> Loading @@ -27,6 +28,7 @@ #include <sstream> #include <android-base/file.h> #include <android-base/logging.h> #include <android-base/parseint.h> #include <android/hidl/manager/1.0/IServiceManager.h> #include <hidl-hash/Hash.h> Loading Loading @@ -220,16 +222,37 @@ const PidInfo* ListCommand::getPidInfoCached(pid_t serverPid) { return &pair.first->second; } // Must process hwbinder services first, then passthrough services. bool ListCommand::shouldReportHalType(const HalType &type) const { return (std::find(mListTypes.begin(), mListTypes.end(), type) != mListTypes.end()); } void ListCommand::forEachTable(const std::function<void(Table &)> &f) { f(mServicesTable); f(mPassthroughRefTable); f(mImplementationsTable); for (const auto& type : mListTypes) { switch (type) { case HalType::BINDERIZED_SERVICES: f(mServicesTable); break; case HalType::PASSTHROUGH_CLIENTS: f(mPassthroughRefTable); break; case HalType::PASSTHROUGH_LIBRARIES: f(mImplementationsTable); break; default: LOG(FATAL) << __func__ << "Unknown HAL type."; } } } void ListCommand::forEachTable(const std::function<void(const Table &)> &f) const { f(mServicesTable); f(mPassthroughRefTable); f(mImplementationsTable); for (const auto& type : mListTypes) { switch (type) { case HalType::BINDERIZED_SERVICES: f(mServicesTable); break; case HalType::PASSTHROUGH_CLIENTS: f(mPassthroughRefTable); break; case HalType::PASSTHROUGH_LIBRARIES: f(mImplementationsTable); break; default: LOG(FATAL) << __func__ << "Unknown HAL type."; } } } void ListCommand::postprocess() { Loading Loading @@ -498,6 +521,8 @@ void ListCommand::putEntry(TableEntrySource source, TableEntry &&entry) { } Status ListCommand::fetchAllLibraries(const sp<IServiceManager> &manager) { if (!shouldReportHalType(HalType::PASSTHROUGH_LIBRARIES)) { return OK; } using namespace ::android::hardware; using namespace ::android::hidl::manager::V1_0; using namespace ::android::hidl::base::V1_0; Loading Loading @@ -526,6 +551,8 @@ Status ListCommand::fetchAllLibraries(const sp<IServiceManager> &manager) { } Status ListCommand::fetchPassthrough(const sp<IServiceManager> &manager) { if (!shouldReportHalType(HalType::PASSTHROUGH_CLIENTS)) { return OK; } using namespace ::android::hardware; using namespace ::android::hardware::details; using namespace ::android::hidl::manager::V1_0; Loading Loading @@ -555,8 +582,9 @@ Status ListCommand::fetchPassthrough(const sp<IServiceManager> &manager) { } Status ListCommand::fetchBinderized(const sp<IServiceManager> &manager) { const std::string mode = "hwbinder"; if (!shouldReportHalType(HalType::BINDERIZED_SERVICES)) { return OK; } const std::string mode = "hwbinder"; hidl_vec<hidl_string> fqInstanceNames; // copying out for timeoutIPC auto listRet = timeoutIPC(manager, &IServiceManager::list, [&] (const auto &names) { Loading Loading @@ -790,6 +818,42 @@ void ListCommand::registerAllOptions() { thiz->mNeat = true; return OK; }, "output is machine parsable (no explanatory text).\nCannot be used with --debug."}); mOptions.push_back({'\0', "types", required_argument, v++, [](ListCommand* thiz, const char* arg) { if (!arg) { return USAGE; } static const std::map<std::string, HalType> kHalTypeMap { {"binderized", HalType::BINDERIZED_SERVICES}, {"b", HalType::BINDERIZED_SERVICES}, {"passthrough_clients", HalType::PASSTHROUGH_CLIENTS}, {"c", HalType::PASSTHROUGH_CLIENTS}, {"passthrough_libs", HalType::PASSTHROUGH_LIBRARIES}, {"l", HalType::PASSTHROUGH_LIBRARIES} }; std::vector<std::string> halTypesArgs = split(std::string(arg), ','); for (const auto& halTypeArg : halTypesArgs) { if (halTypeArg.empty()) continue; const auto& halTypeIter = kHalTypeMap.find(halTypeArg); if (halTypeIter == kHalTypeMap.end()) { thiz->err() << "Unrecognized HAL type: " << halTypeArg << std::endl; return USAGE; } // Append unique (non-repeated) HAL types to the reporting list HalType halType = halTypeIter->second; if (std::find(thiz->mListTypes.begin(), thiz->mListTypes.end(), halType) == thiz->mListTypes.end()) { thiz->mListTypes.push_back(halType); } } if (thiz->mListTypes.empty()) { return USAGE; } return OK; }, "comma-separated list of one or more HAL types.\nThe output is restricted to the selected " "association(s). Valid options\nare: (b|binderized), (c|passthrough_clients), and (l|" "passthrough_libs).\nBy default, lists all available HALs."}); } // Create 'longopts' argument to getopt_long. Caller is responsible for maintaining Loading Loading @@ -828,6 +892,7 @@ static std::string getShortOptions(const ListCommand::RegisteredOptions& options } Status ListCommand::parseArgs(const Arg &arg) { mListTypes.clear(); if (mOptions.empty()) { registerAllOptions(); Loading Loading @@ -900,6 +965,12 @@ Status ListCommand::parseArgs(const Arg &arg) { } } // By default, list all HAL types if (mListTypes.empty()) { mListTypes = {HalType::BINDERIZED_SERVICES, HalType::PASSTHROUGH_CLIENTS, HalType::PASSTHROUGH_LIBRARIES}; } forEachTable([this] (Table& table) { table.setSelectedColumns(this->mSelectedColumns); }); Loading @@ -918,22 +989,6 @@ Status ListCommand::main(const Arg &arg) { return status; } static std::vector<std::string> splitString(const std::string &s, char c) { std::vector<std::string> components; size_t startPos = 0; size_t matchPos; while ((matchPos = s.find(c, startPos)) != std::string::npos) { components.push_back(s.substr(startPos, matchPos - startPos)); startPos = matchPos + 1; } if (startPos <= s.length()) { components.push_back(s.substr(startPos)); } return components; } const std::string& ListCommand::RegisteredOption::getHelpMessageForArgument() const { static const std::string empty{}; static const std::string optional{"[=<arg>]"}; Loading Loading @@ -969,7 +1024,7 @@ void ListCommand::usage() const { if (!e.longOption.empty()) err() << "--" << e.longOption << e.getHelpMessageForArgument(); err() << ": "; std::vector<std::string> lines = splitString(e.help, '\n'); std::vector<std::string> lines = split(e.help, '\n'); for (const auto& line : lines) { if (&line != &lines.front()) err() << " "; Loading
cmds/lshal/ListCommand.h +13 −0 Original line number Diff line number Diff line Loading @@ -46,6 +46,12 @@ struct PidInfo { uint32_t threadCount; // number of threads total }; enum class HalType { BINDERIZED_SERVICES = 0, PASSTHROUGH_CLIENTS, PASSTHROUGH_LIBRARIES }; class ListCommand : public Command { public: ListCommand(Lshal &lshal) : Command(lshal) {} Loading Loading @@ -128,6 +134,9 @@ protected: bool addEntryWithInstance(const TableEntry &entry, vintf::HalManifest *manifest) const; bool addEntryWithoutInstance(const TableEntry &entry, const vintf::HalManifest *manifest) const; // Helper function. Whether to list entries corresponding to a given HAL type. bool shouldReportHalType(const HalType &type) const; Table mServicesTable{}; Table mPassthroughRefTable{}; Table mImplementationsTable{}; Loading @@ -144,6 +153,10 @@ protected: // If true, explanatory text are not emitted. bool mNeat = false; // Type(s) of HAL associations to list. By default, report all. std::vector<HalType> mListTypes{HalType::BINDERIZED_SERVICES, HalType::PASSTHROUGH_CLIENTS, HalType::PASSTHROUGH_LIBRARIES}; // If an entry does not exist, need to ask /proc/{pid}/cmdline to get it. // If an entry exist but is an empty string, process might have died. // If an entry exist and not empty, it contains the cached content of /proc/{pid}/cmdline. Loading
cmds/lshal/test.cpp +84 −0 Original line number Diff line number Diff line Loading @@ -567,6 +567,90 @@ TEST_F(ListTest, DumpNeat) { EXPECT_EQ("", err.str()); } TEST_F(ListTest, DumpSingleHalType) { const std::string expected = "[fake description 0]\n" "Interface Transport Arch Thread Use Server PTR Clients\n" "a.h.foo1@1.0::IFoo/1 hwbinder 64 11/21 1 0000000000002711 2 4\n" "a.h.foo2@2.0::IFoo/2 hwbinder 64 12/22 2 0000000000002712 3 5\n" "\n"; optind = 1; // mimic Lshal::parseArg() EXPECT_EQ(0u, mockList->main(createArg({"lshal", "-itrepac", "--types=binderized"}))); EXPECT_EQ(expected, out.str()); EXPECT_EQ("", err.str()); } TEST_F(ListTest, DumpReorderedHalTypes) { const std::string expected = "[fake description 0]\n" "Interface Transport Arch Thread Use Server PTR Clients\n" "a.h.foo3@3.0::IFoo/3 passthrough 32 N/A N/A N/A 4 6\n" "a.h.foo4@4.0::IFoo/4 passthrough 32 N/A N/A N/A 5 7\n" "\n" "[fake description 1]\n" "Interface Transport Arch Thread Use Server PTR Clients\n" "a.h.foo5@5.0::IFoo/5 passthrough 32 N/A N/A N/A 6 8\n" "a.h.foo6@6.0::IFoo/6 passthrough 32 N/A N/A N/A 7 9\n" "\n" "[fake description 2]\n" "Interface Transport Arch Thread Use Server PTR Clients\n" "a.h.foo1@1.0::IFoo/1 hwbinder 64 11/21 1 0000000000002711 2 4\n" "a.h.foo2@2.0::IFoo/2 hwbinder 64 12/22 2 0000000000002712 3 5\n" "\n"; optind = 1; // mimic Lshal::parseArg() EXPECT_EQ(0u, mockList->main(createArg({"lshal", "-itrepac", "--types=passthrough_clients", "--types=passthrough_libs", "--types=binderized"}))); EXPECT_EQ(expected, out.str()); EXPECT_EQ("", err.str()); } TEST_F(ListTest, DumpAbbreviatedHalTypes) { const std::string expected = "[fake description 0]\n" "Interface Transport Arch Thread Use Server PTR Clients\n" "a.h.foo3@3.0::IFoo/3 passthrough 32 N/A N/A N/A 4 6\n" "a.h.foo4@4.0::IFoo/4 passthrough 32 N/A N/A N/A 5 7\n" "\n" "[fake description 1]\n" "Interface Transport Arch Thread Use Server PTR Clients\n" "a.h.foo5@5.0::IFoo/5 passthrough 32 N/A N/A N/A 6 8\n" "a.h.foo6@6.0::IFoo/6 passthrough 32 N/A N/A N/A 7 9\n" "\n"; optind = 1; // mimic Lshal::parseArg() EXPECT_EQ(0u, mockList->main(createArg({"lshal", "-itrepac", "--types=c,l"}))); EXPECT_EQ(expected, out.str()); EXPECT_EQ("", err.str()); } TEST_F(ListTest, DumpEmptyAndDuplicateHalTypes) { const std::string expected = "[fake description 0]\n" "Interface Transport Arch Thread Use Server PTR Clients\n" "a.h.foo3@3.0::IFoo/3 passthrough 32 N/A N/A N/A 4 6\n" "a.h.foo4@4.0::IFoo/4 passthrough 32 N/A N/A N/A 5 7\n" "\n" "[fake description 1]\n" "Interface Transport Arch Thread Use Server PTR Clients\n" "a.h.foo5@5.0::IFoo/5 passthrough 32 N/A N/A N/A 6 8\n" "a.h.foo6@6.0::IFoo/6 passthrough 32 N/A N/A N/A 7 9\n" "\n"; optind = 1; // mimic Lshal::parseArg() EXPECT_EQ(0u, mockList->main(createArg({"lshal", "-itrepac", "--types=c,l,,,l,l,c,", "--types=passthrough_libs,passthrough_clients"}))); EXPECT_EQ(expected, out.str()); EXPECT_EQ("", err.str()); } TEST_F(ListTest, UnknownHalType) { optind = 1; // mimic Lshal::parseArg() EXPECT_EQ(1u, mockList->main(createArg({"lshal", "-itrepac", "--types=c,a"}))); EXPECT_THAT(err.str(), HasSubstr("Unrecognized HAL type: a")); } class HelpTest : public ::testing::Test { public: void SetUp() override { Loading