diff options
author | 2017-09-07 18:06:13 -0700 | |
---|---|---|
committer | 2017-09-11 13:44:32 -0700 | |
commit | 8bf7316ebc75f9e5b3a6349bbc9c7140e6e90234 (patch) | |
tree | a20865b75d28a6a59bd186380ece39194ef6e639 | |
parent | b2a2ecb642d1dad620bd91f6b875521a55b08224 (diff) |
lshal: add tests for ListCommand::fetch* and dumpVintf
Test: lshal_test
Change-Id: I9e519ec93709ba4dfa7f95e4c5fff60cbda36134
-rw-r--r-- | cmds/lshal/Android.bp | 1 | ||||
-rw-r--r-- | cmds/lshal/ListCommand.cpp | 4 | ||||
-rw-r--r-- | cmds/lshal/ListCommand.h | 16 | ||||
-rw-r--r-- | cmds/lshal/TableEntry.cpp | 22 | ||||
-rw-r--r-- | cmds/lshal/TableEntry.h | 4 | ||||
-rw-r--r-- | cmds/lshal/test.cpp | 297 |
6 files changed, 335 insertions, 9 deletions
diff --git a/cmds/lshal/Android.bp b/cmds/lshal/Android.bp index fab7bfd0e8..47814688a9 100644 --- a/cmds/lshal/Android.bp +++ b/cmds/lshal/Android.bp @@ -61,6 +61,7 @@ cc_test { "libgmock" ], shared_libs: [ + "libvintf", "android.hardware.tests.baz@1.0" ], srcs: [ diff --git a/cmds/lshal/ListCommand.cpp b/cmds/lshal/ListCommand.cpp index 99048afe1c..4550e410a0 100644 --- a/cmds/lshal/ListCommand.cpp +++ b/cmds/lshal/ListCommand.cpp @@ -55,7 +55,7 @@ NullableOStream<std::ostream> ListCommand::err() const { return mLshal.err(); } -std::string getCmdline(pid_t pid) { +std::string ListCommand::parseCmdline(pid_t pid) const { std::ifstream ifs("/proc/" + std::to_string(pid) + "/cmdline"); std::string cmdline; if (!ifs.is_open()) { @@ -70,7 +70,7 @@ const std::string &ListCommand::getCmdline(pid_t pid) { if (pair != mCmdlines.end()) { return pair->second; } - mCmdlines[pid] = ::android::lshal::getCmdline(pid); + mCmdlines[pid] = parseCmdline(pid); return mCmdlines[pid]; } diff --git a/cmds/lshal/ListCommand.h b/cmds/lshal/ListCommand.h index 64d33ca0fe..9833d43de5 100644 --- a/cmds/lshal/ListCommand.h +++ b/cmds/lshal/ListCommand.h @@ -36,9 +36,16 @@ namespace lshal { class Lshal; +struct PidInfo { + std::map<uint64_t, Pids> refPids; // pids that are referenced + uint32_t threadUsage; // number of threads in use + uint32_t threadCount; // number of threads total +}; + class ListCommand { public: ListCommand(Lshal &lshal); + virtual ~ListCommand() = default; Status main(const std::string &command, const Arg &arg); protected: Status parseArgs(const std::string &command, const Arg &arg); @@ -50,12 +57,7 @@ protected: Status fetchBinderized(const sp<::android::hidl::manager::V1_0::IServiceManager> &manager); Status fetchAllLibraries(const sp<::android::hidl::manager::V1_0::IServiceManager> &manager); - struct PidInfo { - std::map<uint64_t, Pids> refPids; // pids that are referenced - uint32_t threadUsage; // number of threads in use - uint32_t threadCount; // number of threads total - }; - bool getPidInfo(pid_t serverPid, PidInfo *info) const; + virtual bool getPidInfo(pid_t serverPid, PidInfo *info) const; void dumpTable(const NullableOStream<std::ostream>& out) const; void dumpVintf(const NullableOStream<std::ostream>& out) const; @@ -64,6 +66,8 @@ protected: const std::string &serverCmdline, const std::string &address, const std::string &clients, const std::string &clientCmdlines) const; void addLine(TextTable *table, const TableEntry &entry); + // Read and return /proc/{pid}/cmdline. + virtual std::string parseCmdline(pid_t pid) const; // Return /proc/{pid}/cmdline if it exists, else empty string. const std::string &getCmdline(pid_t pid); // Call getCmdline on all pid in pids. If it returns empty string, the process might diff --git a/cmds/lshal/TableEntry.cpp b/cmds/lshal/TableEntry.cpp index dc6ccaeda5..eac0f2144a 100644 --- a/cmds/lshal/TableEntry.cpp +++ b/cmds/lshal/TableEntry.cpp @@ -152,5 +152,27 @@ TextTable MergedTable::createTextTable() { return textTable; } +bool TableEntry::operator==(const TableEntry& other) const { + if (this == &other) { + return true; + } + return interfaceName == other.interfaceName && transport == other.transport && + serverPid == other.serverPid && threadUsage == other.threadUsage && + threadCount == other.threadCount && serverCmdline == other.serverCmdline && + serverObjectAddress == other.serverObjectAddress && clientPids == other.clientPids && + clientCmdlines == other.clientCmdlines && arch == other.arch; +} + +std::string TableEntry::to_string() const { + std::stringstream ss; + ss << "name=" << interfaceName << ";transport=" << transport << ";thread=" << getThreadUsage() + << ";server=" << serverPid + << "(" << serverObjectAddress << ";" << serverCmdline << ");clients=[" + << join(clientPids, ";") << "](" << join(clientCmdlines, ";") << ");arch=" + << getArchString(arch); + return ss.str(); + +} + } // namespace lshal } // namespace android diff --git a/cmds/lshal/TableEntry.h b/cmds/lshal/TableEntry.h index dac8b5e3b7..7a3b22ea67 100644 --- a/cmds/lshal/TableEntry.h +++ b/cmds/lshal/TableEntry.h @@ -85,6 +85,9 @@ struct TableEntry { } std::string getField(TableColumnType type) const; + + bool operator==(const TableEntry& other) const; + std::string to_string() const; }; using SelectedColumns = std::vector<TableColumnType>; @@ -97,6 +100,7 @@ public: Entries::const_iterator begin() const { return mEntries.begin(); } Entries::iterator end() { return mEntries.end(); } Entries::const_iterator end() const { return mEntries.end(); } + size_t size() const { return mEntries.size(); } void add(TableEntry&& entry) { mEntries.push_back(std::move(entry)); } diff --git a/cmds/lshal/test.cpp b/cmds/lshal/test.cpp index 6cdec29ad3..44b196e11f 100644 --- a/cmds/lshal/test.cpp +++ b/cmds/lshal/test.cpp @@ -26,6 +26,7 @@ #include <gmock/gmock.h> #include <android/hardware/tests/baz/1.0/IQuux.h> #include <hidl/HidlTransportSupport.h> +#include <vintf/parse_xml.h> #include "ListCommand.h" #include "Lshal.h" @@ -34,6 +35,7 @@ using namespace testing; +using ::android::hidl::base::V1_0::DebugInfo; using ::android::hidl::base::V1_0::IBase; using ::android::hidl::manager::V1_0::IServiceManager; using ::android::hidl::manager::V1_0::IServiceNotification; @@ -42,6 +44,8 @@ using ::android::hardware::hidl_handle; using ::android::hardware::hidl_string; using ::android::hardware::hidl_vec; +using InstanceDebugInfo = IServiceManager::InstanceDebugInfo; + namespace android { namespace hardware { namespace tests { @@ -77,7 +81,6 @@ struct Quux : android::hardware::tests::baz::V1_0::IQuux { namespace lshal { - class MockServiceManager : public IServiceManager { public: template<typename T> @@ -181,9 +184,17 @@ public: MockListCommand(Lshal* lshal) : ListCommand(*lshal) {} 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); } + Status fetch() { return ListCommand::fetch(); } + void dumpVintf(const NullableOStream<std::ostream>& out) { + return ListCommand::dumpVintf(out); + } + + MOCK_CONST_METHOD2(getPidInfo, bool(pid_t, PidInfo*)); + MOCK_CONST_METHOD1(parseCmdline, std::string(pid_t)); }; class ListParseArgsTest : public ::testing::Test { @@ -233,6 +244,290 @@ TEST_F(ListParseArgsTest, DebugAndNeat) { EXPECT_THAT(output.str(), StrNe("")); } +/// Fetch Test + +// A set of deterministic functions to generate fake debug infos. +static uint64_t getPtr(pid_t serverId) { return 10000 + serverId; } +static std::vector<pid_t> getClients(pid_t serverId) { + return {serverId + 1, serverId + 3}; +} +static PidInfo getPidInfoFromId(pid_t serverId) { + PidInfo info; + info.refPids[getPtr(serverId)] = getClients(serverId); + info.threadUsage = 10 + serverId; + info.threadCount = 20 + serverId; + return info; +} +static std::string getInterfaceName(pid_t serverId) { + return "a.h.foo" + std::to_string(serverId) + "@" + std::to_string(serverId) + ".0::IFoo"; +} +static std::string getInstanceName(pid_t serverId) { + return std::to_string(serverId); +} +static pid_t getIdFromInstanceName(const hidl_string& instance) { + return atoi(instance.c_str()); +} +static std::string getFqInstanceName(pid_t serverId) { + return getInterfaceName(serverId) + "/" + getInstanceName(serverId); +} +static std::string getCmdlineFromId(pid_t serverId) { + if (serverId == NO_PID) return ""; + return "command_line_" + std::to_string(serverId); +} + +// Fake service returned by mocked IServiceManager::get. +class TestService : public IBase { +public: + TestService(DebugInfo&& info) : mInfo(std::move(info)) {} + hardware::Return<void> getDebugInfo(getDebugInfo_cb cb) override { + cb(mInfo); + return hardware::Void(); + } +private: + DebugInfo mInfo; +}; + +class ListTest : public ::testing::Test { +public: + void SetUp() override { + initMockServiceManager(); + lshal = std::make_unique<Lshal>(out, err, serviceManager, passthruManager); + initMockList(); + } + + void initMockList() { + mockList = std::make_unique<NiceMock<MockListCommand>>(lshal.get()); + ON_CALL(*mockList, getPidInfo(_,_)).WillByDefault(Invoke( + [](pid_t serverPid, PidInfo* info) { + *info = getPidInfoFromId(serverPid); + return true; + })); + ON_CALL(*mockList, parseCmdline(_)).WillByDefault(Invoke(&getCmdlineFromId)); + } + + void initMockServiceManager() { + serviceManager = new testing::NiceMock<MockServiceManager>(); + passthruManager = new testing::NiceMock<MockServiceManager>(); + using A = DebugInfo::Architecture; + ON_CALL(*serviceManager, list(_)).WillByDefault(Invoke( + [] (IServiceManager::list_cb cb) { + cb({ getFqInstanceName(1), getFqInstanceName(2) }); + return hardware::Void(); + })); + + ON_CALL(*serviceManager, get(_, _)).WillByDefault(Invoke( + [&](const hidl_string&, const hidl_string& instance) { + int id = getIdFromInstanceName(instance); + return sp<IBase>(new TestService({ id /* pid */, getPtr(id), A::IS_64BIT })); + })); + + ON_CALL(*serviceManager, debugDump(_)).WillByDefault(Invoke( + [] (IServiceManager::debugDump_cb cb) { + cb({InstanceDebugInfo{getInterfaceName(3), getInstanceName(3), 3, + getClients(3), A::IS_32BIT}, + InstanceDebugInfo{getInterfaceName(4), getInstanceName(4), 4, + getClients(4), A::IS_32BIT}}); + return hardware::Void(); + })); + + ON_CALL(*passthruManager, debugDump(_)).WillByDefault(Invoke( + [] (IServiceManager::debugDump_cb cb) { + cb({InstanceDebugInfo{getInterfaceName(5), getInstanceName(5), 5, + getClients(5), A::IS_32BIT}, + InstanceDebugInfo{getInterfaceName(6), getInstanceName(6), 6, + getClients(6), A::IS_32BIT}}); + return hardware::Void(); + })); + } + + std::stringstream err; + std::stringstream out; + std::unique_ptr<Lshal> lshal; + std::unique_ptr<MockListCommand> mockList; + sp<MockServiceManager> serviceManager; + sp<MockServiceManager> passthruManager; +}; + +TEST_F(ListTest, Fetch) { + EXPECT_EQ(0u, mockList->fetch()); + std::array<std::string, 6> transports{{"hwbinder", "hwbinder", "passthrough", + "passthrough", "passthrough", "passthrough"}}; + std::array<Architecture, 6> archs{{ARCH64, ARCH64, ARCH32, ARCH32, ARCH32, ARCH32}}; + int id = 1; + mockList->forEachTable([&](const Table& table) { + ASSERT_EQ(2u, table.size()); + for (const auto& entry : table) { + const auto& transport = transports[id - 1]; + TableEntry expected{ + .interfaceName = getFqInstanceName(id), + .transport = transport, + .serverPid = transport == "hwbinder" ? id : NO_PID, + .threadUsage = transport == "hwbinder" ? getPidInfoFromId(id).threadUsage : 0, + .threadCount = transport == "hwbinder" ? getPidInfoFromId(id).threadCount : 0, + .serverCmdline = {}, + .serverObjectAddress = transport == "hwbinder" ? getPtr(id) : NO_PTR, + .clientPids = getClients(id), + .clientCmdlines = {}, + .arch = archs[id - 1], + }; + EXPECT_EQ(expected, entry) << expected.to_string() << " vs. " << entry.to_string(); + + ++id; + } + }); + +} + +TEST_F(ListTest, DumpVintf) { + const std::string expected = + "<!-- \n" + " This is a skeleton device manifest. Notes: \n" + " 1. android.hidl.*, android.frameworks.*, android.system.* are not included.\n" + " 2. If a HAL is supported in both hwbinder and passthrough transport, \n" + " only hwbinder is shown.\n" + " 3. It is likely that HALs in passthrough transport does not have\n" + " <interface> declared; users will have to write them by hand.\n" + " 4. A HAL with lower minor version can be overridden by a HAL with\n" + " higher minor version if they have the same name and major version.\n" + " 5. sepolicy version is set to 0.0. It is recommended that the entry\n" + " is removed from the manifest file and written by assemble_vintf\n" + " at build time.\n" + "-->\n" + "<manifest version=\"1.0\" type=\"device\">\n" + " <hal format=\"hidl\">\n" + " <name>a.h.foo1</name>\n" + " <transport>hwbinder</transport>\n" + " <version>1.0</version>\n" + " <interface>\n" + " <name>IFoo</name>\n" + " <instance>1</instance>\n" + " </interface>\n" + " </hal>\n" + " <hal format=\"hidl\">\n" + " <name>a.h.foo2</name>\n" + " <transport>hwbinder</transport>\n" + " <version>2.0</version>\n" + " <interface>\n" + " <name>IFoo</name>\n" + " <instance>2</instance>\n" + " </interface>\n" + " </hal>\n" + " <hal format=\"hidl\">\n" + " <name>a.h.foo3</name>\n" + " <transport arch=\"32\">passthrough</transport>\n" + " <version>3.0</version>\n" + " <interface>\n" + " <name>IFoo</name>\n" + " <instance>3</instance>\n" + " </interface>\n" + " </hal>\n" + " <hal format=\"hidl\">\n" + " <name>a.h.foo4</name>\n" + " <transport arch=\"32\">passthrough</transport>\n" + " <version>4.0</version>\n" + " <interface>\n" + " <name>IFoo</name>\n" + " <instance>4</instance>\n" + " </interface>\n" + " </hal>\n" + " <hal format=\"hidl\">\n" + " <name>a.h.foo5</name>\n" + " <transport arch=\"32\">passthrough</transport>\n" + " <version>5.0</version>\n" + " </hal>\n" + " <hal format=\"hidl\">\n" + " <name>a.h.foo6</name>\n" + " <transport arch=\"32\">passthrough</transport>\n" + " <version>6.0</version>\n" + " </hal>\n" + " <sepolicy>\n" + " <version>0.0</version>\n" + " </sepolicy>\n" + "</manifest>\n"; + + optind = 1; // mimic Lshal::parseArg() + EXPECT_EQ(0u, mockList->main(createArg({"lshal", "--init-vintf"}))); + EXPECT_EQ(expected, out.str()); + EXPECT_EQ("", err.str()); + + vintf::HalManifest m; + EXPECT_EQ(true, vintf::gHalManifestConverter(&m, out.str())) + << "--init-vintf does not emit valid HAL manifest: " + << vintf::gHalManifestConverter.lastError(); +} + +TEST_F(ListTest, Dump) { + const std::string expected = + "All binderized services (registered services through hwservicemanager)\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" + "All interfaces that getService() has ever return as a passthrough interface;\n" + "PIDs / processes shown below might be inaccurate because the process\n" + "might have relinquished the interface or might have died.\n" + "The Server / Server CMD column can be ignored.\n" + "The Clients / Clients CMD column shows all process that have ever dlopen'ed \n" + "the library and successfully fetched the passthrough implementation.\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" + "All available passthrough implementations (all -impl.so files)\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"}))); + EXPECT_EQ(expected, out.str()); + EXPECT_EQ("", err.str()); +} + +TEST_F(ListTest, DumpCmdline) { + const std::string expected = + "All binderized services (registered services through hwservicemanager)\n" + "Interface Transport Arch Thread Use Server CMD PTR Clients CMD\n" + "a.h.foo1@1.0::IFoo/1 hwbinder 64 11/21 command_line_1 0000000000002711 command_line_2;command_line_4\n" + "a.h.foo2@2.0::IFoo/2 hwbinder 64 12/22 command_line_2 0000000000002712 command_line_3;command_line_5\n" + "\n" + "All interfaces that getService() has ever return as a passthrough interface;\n" + "PIDs / processes shown below might be inaccurate because the process\n" + "might have relinquished the interface or might have died.\n" + "The Server / Server CMD column can be ignored.\n" + "The Clients / Clients CMD column shows all process that have ever dlopen'ed \n" + "the library and successfully fetched the passthrough implementation.\n" + "Interface Transport Arch Thread Use Server CMD PTR Clients CMD\n" + "a.h.foo3@3.0::IFoo/3 passthrough 32 N/A N/A command_line_4;command_line_6\n" + "a.h.foo4@4.0::IFoo/4 passthrough 32 N/A N/A command_line_5;command_line_7\n" + "\n" + "All available passthrough implementations (all -impl.so files)\n" + "Interface Transport Arch Thread Use Server CMD PTR Clients CMD\n" + "a.h.foo5@5.0::IFoo/5 passthrough 32 N/A N/A command_line_6;command_line_8\n" + "a.h.foo6@6.0::IFoo/6 passthrough 32 N/A N/A command_line_7;command_line_9\n" + "\n"; + + optind = 1; // mimic Lshal::parseArg() + EXPECT_EQ(0u, mockList->main(createArg({"lshal", "-itrepacm"}))); + EXPECT_EQ(expected, out.str()); + EXPECT_EQ("", err.str()); +} + +TEST_F(ListTest, DumpNeat) { + const std::string expected = + "a.h.foo1@1.0::IFoo/1 11/21 1 2 4\n" + "a.h.foo2@2.0::IFoo/2 12/22 2 3 5\n" + "a.h.foo3@3.0::IFoo/3 N/A N/A 4 6\n" + "a.h.foo4@4.0::IFoo/4 N/A N/A 5 7\n" + "a.h.foo5@5.0::IFoo/5 N/A N/A 6 8\n" + "a.h.foo6@6.0::IFoo/6 N/A N/A 7 9\n"; + + optind = 1; // mimic Lshal::parseArg() + EXPECT_EQ(0u, mockList->main(createArg({"lshal", "--neat"}))); + EXPECT_EQ(expected, out.str()); + EXPECT_EQ("", err.str()); +} } // namespace lshal } // namespace android |