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