lshal: add tests for ListCommand::fetch* and dumpVintf
Test: lshal_test
Change-Id: I9e519ec93709ba4dfa7f95e4c5fff60cbda36134
diff --git a/cmds/lshal/Android.bp b/cmds/lshal/Android.bp
index fab7bfd..4781468 100644
--- a/cmds/lshal/Android.bp
+++ b/cmds/lshal/Android.bp
@@ -61,6 +61,7 @@
"libgmock"
],
shared_libs: [
+ "libvintf",
"android.hardware.tests.baz@1.0"
],
srcs: [
diff --git a/cmds/lshal/ListCommand.cpp b/cmds/lshal/ListCommand.cpp
index 99048af..4550e41 100644
--- a/cmds/lshal/ListCommand.cpp
+++ b/cmds/lshal/ListCommand.cpp
@@ -55,7 +55,7 @@
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 @@
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 64d33ca..9833d43 100644
--- a/cmds/lshal/ListCommand.h
+++ b/cmds/lshal/ListCommand.h
@@ -36,9 +36,16 @@
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 @@
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 @@
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 dc6ccae..eac0f21 100644
--- a/cmds/lshal/TableEntry.cpp
+++ b/cmds/lshal/TableEntry.cpp
@@ -152,5 +152,27 @@
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 dac8b5e..7a3b22e 100644
--- a/cmds/lshal/TableEntry.h
+++ b/cmds/lshal/TableEntry.h
@@ -85,6 +85,9 @@
}
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 @@
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 6cdec29..44b196e 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_string;
using ::android::hardware::hidl_vec;
+using InstanceDebugInfo = IServiceManager::InstanceDebugInfo;
+
namespace android {
namespace hardware {
namespace tests {
@@ -77,7 +81,6 @@
namespace lshal {
-
class MockServiceManager : public IServiceManager {
public:
template<typename T>
@@ -181,9 +184,17 @@
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 @@
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