Merge "Keep standalone devices in VrFlinger mode."
diff --git a/cmds/lshal/Android.bp b/cmds/lshal/Android.bp
index 2eed1ce..4781468 100644
--- a/cmds/lshal/Android.bp
+++ b/cmds/lshal/Android.bp
@@ -28,6 +28,7 @@
         "Lshal.cpp",
         "ListCommand.cpp",
         "PipeRelay.cpp",
+        "TableEntry.cpp",
         "TextTable.cpp",
         "utils.cpp",
     ],
@@ -60,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 31c42e7..4550e41 100644
--- a/cmds/lshal/ListCommand.cpp
+++ b/cmds/lshal/ListCommand.cpp
@@ -35,7 +35,6 @@
 
 #include "Lshal.h"
 #include "PipeRelay.h"
-#include "TextTable.h"
 #include "Timeout.h"
 #include "utils.h"
 
@@ -45,10 +44,18 @@
 namespace android {
 namespace lshal {
 
-ListCommand::ListCommand(Lshal &lshal) : mLshal(lshal), mErr(lshal.err()), mOut(lshal.out()) {
+ListCommand::ListCommand(Lshal &lshal) : mLshal(lshal) {
 }
 
-std::string getCmdline(pid_t pid) {
+NullableOStream<std::ostream> ListCommand::out() const {
+    return mLshal.out();
+}
+
+NullableOStream<std::ostream> ListCommand::err() const {
+    return mLshal.err();
+}
+
+std::string ListCommand::parseCmdline(pid_t pid) const {
     std::ifstream ifs("/proc/" + std::to_string(pid) + "/cmdline");
     std::string cmdline;
     if (!ifs.is_open()) {
@@ -63,7 +70,7 @@
     if (pair != mCmdlines.end()) {
         return pair->second;
     }
-    mCmdlines[pid] = ::android::lshal::getCmdline(pid);
+    mCmdlines[pid] = parseCmdline(pid);
     return mCmdlines[pid];
 }
 
@@ -114,7 +121,7 @@
             uint64_t ptr;
             if (!::android::base::ParseUint(ptrString.c_str(), &ptr)) {
                 // Should not reach here, but just be tolerant.
-                mErr << "Could not parse number " << ptrString << std::endl;
+                err() << "Could not parse number " << ptrString << std::endl;
                 return;
             }
             const std::string proc = " proc ";
@@ -123,7 +130,7 @@
                 for (const std::string &pidStr : split(line.substr(pos + proc.size()), ' ')) {
                     int32_t pid;
                     if (!::android::base::ParseInt(pidStr, &pid)) {
-                        mErr << "Could not parse number " << pidStr << std::endl;
+                        err() << "Could not parse number " << pidStr << std::endl;
                         return;
                     }
                     pidInfo->refPids[ptr].push_back(pid);
@@ -205,48 +212,18 @@
             }
         }
     }
-}
 
-void ListCommand::addLine(TextTable *textTable, const std::string &interfaceName,
-                          const std::string &transport, const std::string &arch,
-                          const std::string &threadUsage, const std::string &server,
-                          const std::string &serverCmdline, const std::string &address,
-                          const std::string &clients, const std::string &clientCmdlines) const {
-    std::vector<std::string> columns;
-    for (TableColumnType type : mSelectedColumns) {
-        switch (type) {
-            case TableColumnType::INTERFACE_NAME: {
-                columns.push_back(interfaceName);
-            } break;
-            case TableColumnType::TRANSPORT: {
-                columns.push_back(transport);
-            } break;
-            case TableColumnType::ARCH: {
-                columns.push_back(arch);
-            } break;
-            case TableColumnType::THREADS: {
-                columns.push_back(threadUsage);
-            } break;
-            case TableColumnType::SERVER_ADDR: {
-                columns.push_back(address);
-            } break;
-            case TableColumnType::SERVER_PID: {
-                if (mEnableCmdlines) {
-                    columns.push_back(serverCmdline);
-                } else {
-                    columns.push_back(server);
-                }
-            } break;
-            case TableColumnType::CLIENT_PIDS: {
-                if (mEnableCmdlines) {
-                    columns.push_back(clientCmdlines);
-                } else {
-                    columns.push_back(clients);
-                }
-            } break;
-        }
-    }
-    textTable->add(std::move(columns));
+    mServicesTable.setDescription(
+            "All binderized services (registered services through hwservicemanager)");
+    mPassthroughRefTable.setDescription(
+            "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.");
+    mImplementationsTable.setDescription(
+            "All available passthrough implementations (all -impl.so files)");
 }
 
 static inline bool findAndBumpVersion(vintf::ManifestHal* hal, const vintf::Version& version) {
@@ -259,9 +236,9 @@
     return false;
 }
 
-void ListCommand::dumpVintf() const {
+void ListCommand::dumpVintf(const NullableOStream<std::ostream>& out) const {
     using vintf::operator|=;
-    mOut << "<!-- " << std::endl
+    out << "<!-- " << std::endl
          << "    This is a skeleton device manifest. Notes: " << std::endl
          << "    1. android.hidl.*, android.frameworks.*, android.system.* are not included." << std::endl
          << "    2. If a HAL is supported in both hwbinder and passthrough transport, " << std::endl
@@ -288,7 +265,7 @@
             auto splittedFqInstanceName = splitFirst(fqInstanceName, '/');
             FQName fqName(splittedFqInstanceName.first);
             if (!fqName.isValid()) {
-                mErr << "Warning: '" << splittedFqInstanceName.first
+                err() << "Warning: '" << splittedFqInstanceName.first
                      << "' is not a valid FQName." << std::endl;
                 continue;
             }
@@ -321,12 +298,12 @@
                         arch = vintf::Arch::ARCH_32_64; break;
                     case lshal::ARCH_UNKNOWN: // fallthrough
                     default:
-                        mErr << "Warning: '" << fqName.package()
+                        err() << "Warning: '" << fqName.package()
                              << "' doesn't have bitness info, assuming 32+64." << std::endl;
                         arch = vintf::Arch::ARCH_32_64;
                 }
             } else {
-                mErr << "Warning: '" << entry.transport << "' is not a valid transport." << std::endl;
+                err() << "Warning: '" << entry.transport << "' is not a valid transport." << std::endl;
                 continue;
             }
 
@@ -334,7 +311,7 @@
             for (vintf::ManifestHal *hal : manifest.getHals(fqName.package())) {
                 if (hal->transport() != transport) {
                     if (transport != vintf::Transport::PASSTHROUGH) {
-                        mErr << "Fatal: should not reach here. Generated result may be wrong for '"
+                        err() << "Fatal: should not reach here. Generated result may be wrong for '"
                              << hal->name << "'."
                              << std::endl;
                     }
@@ -365,29 +342,11 @@
                     .versions = {version},
                     .transportArch = {transport, arch},
                     .interfaces = interfaces})) {
-                mErr << "Warning: cannot add hal '" << fqInstanceName << "'" << std::endl;
+                err() << "Warning: cannot add hal '" << fqInstanceName << "'" << std::endl;
             }
         }
     });
-    mOut << vintf::gHalManifestConverter(manifest);
-}
-
-static const std::string &getArchString(Architecture arch) {
-    static const std::string sStr64 = "64";
-    static const std::string sStr32 = "32";
-    static const std::string sStrBoth = "32+64";
-    static const std::string sStrUnknown = "";
-    switch (arch) {
-        case ARCH64:
-            return sStr64;
-        case ARCH32:
-            return sStr32;
-        case ARCH_BOTH:
-            return sStrBoth;
-        case ARCH_UNKNOWN: // fall through
-        default:
-            return sStrUnknown;
-    }
+    out << vintf::gHalManifestConverter(manifest);
 }
 
 static Architecture fromBaseArchitecture(::android::hidl::base::V1_0::DebugInfo::Architecture a) {
@@ -402,78 +361,54 @@
     }
 }
 
-void ListCommand::addLine(TextTable *table, const TableEntry &entry) {
-    addLine(table, entry.interfaceName, entry.transport, getArchString(entry.arch),
-            entry.getThreadUsage(),
-            entry.serverPid == NO_PID ? "N/A" : std::to_string(entry.serverPid),
-            entry.serverCmdline,
-            entry.serverObjectAddress == NO_PTR ? "N/A" : toHexString(entry.serverObjectAddress),
-            join(entry.clientPids, " "), join(entry.clientCmdlines, ";"));
-}
-
-void ListCommand::dumpTable() {
+void ListCommand::dumpTable(const NullableOStream<std::ostream>& out) const {
     if (mNeat) {
-        TextTable textTable;
-        forEachTable([this, &textTable](const Table &table) {
-            for (const auto &entry : table) addLine(&textTable, entry);
-        });
-        textTable.dump(mOut.buf());
+        MergedTable({&mServicesTable, &mPassthroughRefTable, &mImplementationsTable})
+            .createTextTable().dump(out.buf());
         return;
     }
 
-    mServicesTable.description =
-            "All binderized services (registered services through hwservicemanager)";
-    mPassthroughRefTable.description =
-            "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.";
-    mImplementationsTable.description =
-            "All available passthrough implementations (all -impl.so files)";
+    forEachTable([this, &out](const Table &table) {
 
-    forEachTable([this](const Table &table) {
-        TextTable textTable;
-
-        textTable.add(table.description);
-        addLine(&textTable, "Interface", "Transport", "Arch", "Thread Use", "Server", "Server CMD",
-                "PTR", "Clients", "Clients CMD");
-
-        for (const auto &entry : table) {
-            addLine(&textTable, entry);
-            // We're only interested in dumping debug info for already
-            // instantiated services. There's little value in dumping the
-            // debug info for a service we create on the fly, so we only operate
-            // on the "mServicesTable".
-            if (mEmitDebugInfo && &table == &mServicesTable) {
-                std::stringstream out;
-                auto pair = splitFirst(entry.interfaceName, '/');
-                mLshal.emitDebugInfo(pair.first, pair.second, {}, out,
+        // We're only interested in dumping debug info for already
+        // instantiated services. There's little value in dumping the
+        // debug info for a service we create on the fly, so we only operate
+        // on the "mServicesTable".
+        std::function<std::string(const std::string&)> emitDebugInfo = nullptr;
+        if (mEmitDebugInfo && &table == &mServicesTable) {
+            emitDebugInfo = [this](const auto& iName) {
+                std::stringstream ss;
+                auto pair = splitFirst(iName, '/');
+                mLshal.emitDebugInfo(pair.first, pair.second, {}, ss,
                                      NullableOStream<std::ostream>(nullptr));
-                textTable.add(out.str());
-            }
+                return ss.str();
+            };
         }
-
-        // Add empty line after each table
-        textTable.add();
-
-        textTable.dump(mOut.buf());
+        table.createTextTable(mNeat, emitDebugInfo).dump(out.buf());
+        out << std::endl;
     });
 }
 
-void ListCommand::dump() {
-    if (mVintf) {
-        dumpVintf();
-        if (!!mFileOutput) {
-            mFileOutput.buf().close();
-            delete &mFileOutput.buf();
-            mFileOutput = nullptr;
-        }
-        mOut = std::cout;
-    } else {
-        dumpTable();
+Status ListCommand::dump() {
+    auto dump = mVintf ? &ListCommand::dumpVintf : &ListCommand::dumpTable;
+
+    if (mFileOutputPath.empty()) {
+        (*this.*dump)(out());
+        return OK;
     }
+
+    std::ofstream fileOutput(mFileOutputPath);
+    if (!fileOutput.is_open()) {
+        err() << "Could not open file '" << mFileOutputPath << "'." << std::endl;
+        return IO_ERROR;
+    }
+    chown(mFileOutputPath.c_str(), AID_SHELL, AID_SHELL);
+
+    (*this.*dump)(NullableOStream<std::ostream>(fileOutput));
+
+    fileOutput.flush();
+    fileOutput.close();
+    return OK;
 }
 
 void ListCommand::putEntry(TableEntrySource source, TableEntry &&entry) {
@@ -486,10 +421,10 @@
         case LIST_DLLIB :
             table = &mImplementationsTable; break;
         default:
-            mErr << "Error: Unknown source of entry " << source << std::endl;
+            err() << "Error: Unknown source of entry " << source << std::endl;
     }
     if (table) {
-        table->entries.push_back(std::forward<TableEntry>(entry));
+        table->add(std::forward<TableEntry>(entry));
     }
 }
 
@@ -517,7 +452,7 @@
         }
     });
     if (!ret.isOk()) {
-        mErr << "Error: Failed to call list on getPassthroughServiceManager(): "
+        err() << "Error: Failed to call list on getPassthroughServiceManager(): "
              << ret.description() << std::endl;
         return DUMP_ALL_LIBS_ERROR;
     }
@@ -547,7 +482,7 @@
         }
     });
     if (!ret.isOk()) {
-        mErr << "Error: Failed to call debugDump on defaultServiceManager(): "
+        err() << "Error: Failed to call debugDump on defaultServiceManager(): "
              << ret.description() << std::endl;
         return DUMP_PASSTHROUGH_ERROR;
     }
@@ -567,7 +502,7 @@
         fqInstanceNames = names;
     });
     if (!listRet.isOk()) {
-        mErr << "Error: Failed to list services for " << mode << ": "
+        err() << "Error: Failed to list services for " << mode << ": "
              << listRet.description() << std::endl;
         return DUMP_BINDERIZED_ERROR;
     }
@@ -582,7 +517,7 @@
         const auto &instanceName = pair.second;
         auto getRet = timeoutIPC(manager, &IServiceManager::get, serviceName, instanceName);
         if (!getRet.isOk()) {
-            mErr << "Warning: Skipping \"" << fqInstanceName << "\": "
+            err() << "Warning: Skipping \"" << fqInstanceName << "\": "
                  << "cannot be fetched from service manager:"
                  << getRet.description() << std::endl;
             status |= DUMP_BINDERIZED_ERROR;
@@ -590,7 +525,7 @@
         }
         sp<IBase> service = getRet;
         if (service == nullptr) {
-            mErr << "Warning: Skipping \"" << fqInstanceName << "\": "
+            err() << "Warning: Skipping \"" << fqInstanceName << "\": "
                  << "cannot be fetched from service manager (null)"
                  << std::endl;
             status |= DUMP_BINDERIZED_ERROR;
@@ -603,7 +538,7 @@
             }
         });
         if (!debugRet.isOk()) {
-            mErr << "Warning: Skipping \"" << fqInstanceName << "\": "
+            err() << "Warning: Skipping \"" << fqInstanceName << "\": "
                  << "debugging information cannot be retrieved:"
                  << debugRet.description() << std::endl;
             status |= DUMP_BINDERIZED_ERROR;
@@ -613,7 +548,7 @@
     for (auto &pair : allPids) {
         pid_t serverPid = pair.first;
         if (!getPidInfo(serverPid, &allPids[serverPid])) {
-            mErr << "Warning: no information for PID " << serverPid
+            err() << "Warning: no information for PID " << serverPid
                       << ", are you root?" << std::endl;
             status |= DUMP_BINDERIZED_ERROR;
         }
@@ -654,7 +589,7 @@
     Status status = OK;
     auto bManager = mLshal.serviceManager();
     if (bManager == nullptr) {
-        mErr << "Failed to get defaultServiceManager()!" << std::endl;
+        err() << "Failed to get defaultServiceManager()!" << std::endl;
         status |= NO_BINDERIZED_MANAGER;
     } else {
         status |= fetchBinderized(bManager);
@@ -664,7 +599,7 @@
 
     auto pManager = mLshal.passthroughManager();
     if (pManager == nullptr) {
-        mErr << "Failed to get getPassthroughServiceManager()!" << std::endl;
+        err() << "Failed to get getPassthroughServiceManager()!" << std::endl;
         status |= NO_PASSTHROUGH_MANAGER;
     } else {
         status |= fetchAllLibraries(pManager);
@@ -693,6 +628,9 @@
         { 0,          0,                 0,  0  }
     };
 
+    std::vector<TableColumnType> selectedColumns;
+    bool enableCmdlines = false;
+
     int optionIndex;
     int c;
     // Lshal::parseArgs has set optind to the next option to parse
@@ -710,67 +648,52 @@
             } else if (strcmp(optarg, "pid") == 0 || strcmp(optarg, "p") == 0) {
                 mSortColumn = TableEntry::sortByServerPid;
             } else {
-                mErr << "Unrecognized sorting column: " << optarg << std::endl;
+                err() << "Unrecognized sorting column: " << optarg << std::endl;
                 mLshal.usage(command);
                 return USAGE;
             }
             break;
         }
         case 'v': {
-            if (optarg) {
-                mFileOutput = new std::ofstream{optarg};
-                mOut = mFileOutput;
-                if (!mFileOutput.buf().is_open()) {
-                    mErr << "Could not open file '" << optarg << "'." << std::endl;
-                    return IO_ERROR;
-                }
-            }
             mVintf = true;
+            if (optarg) mFileOutputPath = optarg;
+            break;
         }
         case 'i': {
-            mSelectedColumns.push_back(TableColumnType::INTERFACE_NAME);
+            selectedColumns.push_back(TableColumnType::INTERFACE_NAME);
             break;
         }
         case 't': {
-            mSelectedColumns.push_back(TableColumnType::TRANSPORT);
+            selectedColumns.push_back(TableColumnType::TRANSPORT);
             break;
         }
         case 'r': {
-            mSelectedColumns.push_back(TableColumnType::ARCH);
+            selectedColumns.push_back(TableColumnType::ARCH);
             break;
         }
         case 'p': {
-            mSelectedColumns.push_back(TableColumnType::SERVER_PID);
+            selectedColumns.push_back(TableColumnType::SERVER_PID);
             break;
         }
         case 'a': {
-            mSelectedColumns.push_back(TableColumnType::SERVER_ADDR);
+            selectedColumns.push_back(TableColumnType::SERVER_ADDR);
             break;
         }
         case 'c': {
-            mSelectedColumns.push_back(TableColumnType::CLIENT_PIDS);
+            selectedColumns.push_back(TableColumnType::CLIENT_PIDS);
             break;
         }
         case 'e': {
-            mSelectedColumns.push_back(TableColumnType::THREADS);
+            selectedColumns.push_back(TableColumnType::THREADS);
             break;
         }
         case 'm': {
-            mEnableCmdlines = true;
+            enableCmdlines = true;
             break;
         }
         case 'd': {
             mEmitDebugInfo = true;
-
-            if (optarg) {
-                mFileOutput = new std::ofstream{optarg};
-                mOut = mFileOutput;
-                if (!mFileOutput.buf().is_open()) {
-                    mErr << "Could not open file '" << optarg << "'." << std::endl;
-                    return IO_ERROR;
-                }
-                chown(optarg, AID_SHELL, AID_SHELL);
-            }
+            if (optarg) mFileOutputPath = optarg;
             break;
         }
         case 'n': {
@@ -785,21 +708,37 @@
     }
     if (optind < arg.argc) {
         // see non option
-        mErr << "Unrecognized option `" << arg.argv[optind] << "`" << std::endl;
+        err() << "Unrecognized option `" << arg.argv[optind] << "`" << std::endl;
         mLshal.usage(command);
         return USAGE;
     }
 
     if (mNeat && mEmitDebugInfo) {
-        mErr << "Error: --neat should not be used with --debug." << std::endl;
+        err() << "Error: --neat should not be used with --debug." << std::endl;
         mLshal.usage(command);
         return USAGE;
     }
 
-    if (mSelectedColumns.empty()) {
-        mSelectedColumns = {TableColumnType::INTERFACE_NAME, TableColumnType::THREADS,
+    if (selectedColumns.empty()) {
+        selectedColumns = {TableColumnType::INTERFACE_NAME, TableColumnType::THREADS,
                             TableColumnType::SERVER_PID, TableColumnType::CLIENT_PIDS};
     }
+
+    if (enableCmdlines) {
+        for (size_t i = 0; i < selectedColumns.size(); ++i) {
+            if (selectedColumns[i] == TableColumnType::SERVER_PID) {
+                selectedColumns[i] = TableColumnType::SERVER_CMD;
+            }
+            if (selectedColumns[i] == TableColumnType::CLIENT_PIDS) {
+                selectedColumns[i] = TableColumnType::CLIENT_CMDS;
+            }
+        }
+    }
+
+    forEachTable([&selectedColumns] (Table& table) {
+        table.setSelectedColumns(selectedColumns);
+    });
+
     return OK;
 }
 
@@ -810,7 +749,7 @@
     }
     status = fetch();
     postprocess();
-    dump();
+    status |= dump();
     return status;
 }
 
diff --git a/cmds/lshal/ListCommand.h b/cmds/lshal/ListCommand.h
index 176d5b9..9833d43 100644
--- a/cmds/lshal/ListCommand.h
+++ b/cmds/lshal/ListCommand.h
@@ -36,34 +36,38 @@
 
 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);
-private:
+protected:
     Status parseArgs(const std::string &command, const Arg &arg);
     Status fetch();
     void postprocess();
-    void dump();
+    Status dump();
     void putEntry(TableEntrySource source, TableEntry &&entry);
     Status fetchPassthrough(const sp<::android::hidl::manager::V1_0::IServiceManager> &manager);
     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();
-    void dumpVintf() const;
+    void dumpTable(const NullableOStream<std::ostream>& out) const;
+    void dumpVintf(const NullableOStream<std::ostream>& out) const;
     void addLine(TextTable *table, const std::string &interfaceName, const std::string &transport,
                  const std::string &arch, const std::string &threadUsage, const std::string &server,
                  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
@@ -72,21 +76,18 @@
     void forEachTable(const std::function<void(Table &)> &f);
     void forEachTable(const std::function<void(const Table &)> &f) const;
 
+    NullableOStream<std::ostream> err() const;
+    NullableOStream<std::ostream> out() const;
+
     Lshal &mLshal;
 
     Table mServicesTable{};
     Table mPassthroughRefTable{};
     Table mImplementationsTable{};
 
-    NullableOStream<std::ostream> mErr;
-    NullableOStream<std::ostream> mOut;
-    NullableOStream<std::ofstream> mFileOutput = nullptr;
+    std::string mFileOutputPath;
     TableEntryCompare mSortColumn = nullptr;
-    std::vector<TableColumnType> mSelectedColumns;
-    // If true, cmdlines will be printed instead of pid.
-    bool mEnableCmdlines = false;
 
-    // If true, calls IBase::debug(...) on each service.
     bool mEmitDebugInfo = false;
 
     // If true, output in VINTF format.
diff --git a/cmds/lshal/Lshal.cpp b/cmds/lshal/Lshal.cpp
index ac74775..da45d65 100644
--- a/cmds/lshal/Lshal.cpp
+++ b/cmds/lshal/Lshal.cpp
@@ -109,15 +109,15 @@
             "        Print help message for debug\n";
 
     if (command == "list") {
-        mErr << list;
+        err() << list;
         return;
     }
     if (command == "debug") {
-        mErr << debug;
+        err() << debug;
         return;
     }
 
-    mErr << helpSummary << "\n" << list << "\n" << debug << "\n" << help;
+    err() << helpSummary << "\n" << list << "\n" << debug << "\n" << help;
 }
 
 // A unique_ptr type using a custom deleter function.
@@ -206,7 +206,7 @@
         return OK;
     }
 
-    mErr << arg.argv[0] << ": unrecognized option `" << arg.argv[optind] << "`" << std::endl;
+    err() << arg.argv[0] << ": unrecognized option `" << arg.argv[optind] << "`" << std::endl;
     usage();
     return USAGE;
 }
diff --git a/cmds/lshal/Lshal.h b/cmds/lshal/Lshal.h
index 00db5d0..d3cc4e2 100644
--- a/cmds/lshal/Lshal.h
+++ b/cmds/lshal/Lshal.h
@@ -33,13 +33,14 @@
 class Lshal {
 public:
     Lshal();
+    virtual ~Lshal() {}
     Lshal(std::ostream &out, std::ostream &err,
             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;
-    NullableOStream<std::ostream> err() const;
-    NullableOStream<std::ostream> out() const;
+    virtual NullableOStream<std::ostream> err() const;
+    virtual NullableOStream<std::ostream> out() const;
     const sp<hidl::manager::V1_0::IServiceManager> &serviceManager() const;
     const sp<hidl::manager::V1_0::IServiceManager> &passthroughManager() const;
 
diff --git a/cmds/lshal/TableEntry.cpp b/cmds/lshal/TableEntry.cpp
new file mode 100644
index 0000000..eac0f21
--- /dev/null
+++ b/cmds/lshal/TableEntry.cpp
@@ -0,0 +1,178 @@
+/*
+ * 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.
+ */
+#define LOG_TAG "lshal"
+#include <android-base/logging.h>
+
+#include "TableEntry.h"
+
+#include "TextTable.h"
+#include "utils.h"
+
+namespace android {
+namespace lshal {
+
+static const std::string &getArchString(Architecture arch) {
+    static const std::string sStr64 = "64";
+    static const std::string sStr32 = "32";
+    static const std::string sStrBoth = "32+64";
+    static const std::string sStrUnknown = "";
+    switch (arch) {
+        case ARCH64:
+            return sStr64;
+        case ARCH32:
+            return sStr32;
+        case ARCH_BOTH:
+            return sStrBoth;
+        case ARCH_UNKNOWN: // fall through
+        default:
+            return sStrUnknown;
+    }
+}
+
+static std::string getTitle(TableColumnType type) {
+    switch (type) {
+        case TableColumnType::INTERFACE_NAME: {
+            return "Interface";
+        } break;
+        case TableColumnType::TRANSPORT: {
+            return "Transport";
+        } break;
+        case TableColumnType::SERVER_PID: {
+            return "Server";
+        } break;
+        case TableColumnType::SERVER_CMD: {
+            return "Server CMD";
+        }
+        case TableColumnType::SERVER_ADDR: {
+            return "PTR";
+        } break;
+        case TableColumnType::CLIENT_PIDS: {
+            return "Clients";
+        } break;
+        case TableColumnType::CLIENT_CMDS: {
+            return "Clients CMD";
+        } break;
+        case TableColumnType::ARCH: {
+            return "Arch";
+        } break;
+        case TableColumnType::THREADS: {
+            return "Thread Use";
+        } break;
+        default: {
+            LOG(FATAL) << "Should not reach here.";
+            return "";
+        }
+    }
+}
+
+std::string TableEntry::getField(TableColumnType type) const {
+    switch (type) {
+        case TableColumnType::INTERFACE_NAME: {
+            return interfaceName;
+        } break;
+        case TableColumnType::TRANSPORT: {
+            return transport;
+        } break;
+        case TableColumnType::SERVER_PID: {
+            return serverPid == NO_PID ? "N/A" : std::to_string(serverPid);
+        } break;
+        case TableColumnType::SERVER_CMD: {
+            return serverCmdline;
+        } break;
+        case TableColumnType::SERVER_ADDR: {
+            return serverObjectAddress == NO_PTR ? "N/A" : toHexString(serverObjectAddress);
+        } break;
+        case TableColumnType::CLIENT_PIDS: {
+            return join(clientPids, " ");
+        } break;
+        case TableColumnType::CLIENT_CMDS: {
+            return join(clientCmdlines, ";");
+        } break;
+        case TableColumnType::ARCH: {
+            return getArchString(arch);
+        } break;
+        case TableColumnType::THREADS: {
+            return getThreadUsage();
+        } break;
+        default: {
+            LOG(FATAL) << "Should not reach here.";
+            return "";
+        }
+    }
+}
+
+TextTable Table::createTextTable(bool neat,
+    const std::function<std::string(const std::string&)>& emitDebugInfo) const {
+
+    TextTable textTable;
+    std::vector<std::string> row;
+    if (!neat) {
+        textTable.add(mDescription);
+
+        row.clear();
+        for (TableColumnType type : mSelectedColumns) {
+            row.push_back(getTitle(type));
+        }
+        textTable.add(std::move(row));
+    }
+
+    for (const auto& entry : mEntries) {
+        row.clear();
+        for (TableColumnType type : mSelectedColumns) {
+            row.push_back(entry.getField(type));
+        }
+        textTable.add(std::move(row));
+
+        if (emitDebugInfo) {
+            std::string debugInfo = emitDebugInfo(entry.interfaceName);
+            if (!debugInfo.empty()) textTable.add(debugInfo);
+        }
+    }
+    return textTable;
+}
+
+TextTable MergedTable::createTextTable() {
+    TextTable textTable;
+    for (const Table* table : mTables) {
+        textTable.addAll(table->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 f18f38a..7a3b22e 100644
--- a/cmds/lshal/TableEntry.h
+++ b/cmds/lshal/TableEntry.h
@@ -23,6 +23,8 @@
 #include <vector>
 #include <iostream>
 
+#include "TextTable.h"
+
 namespace android {
 namespace lshal {
 
@@ -43,6 +45,18 @@
 };
 using Architecture = unsigned int;
 
+enum class TableColumnType : unsigned int {
+    INTERFACE_NAME,
+    TRANSPORT,
+    SERVER_PID,
+    SERVER_CMD,
+    SERVER_ADDR,
+    CLIENT_PIDS,
+    CLIENT_CMDS,
+    ARCH,
+    THREADS,
+};
+
 struct TableEntry {
     std::string interfaceName;
     std::string transport;
@@ -69,29 +83,50 @@
 
         return std::to_string(threadUsage) + "/" + std::to_string(threadCount);
     }
+
+    std::string getField(TableColumnType type) const;
+
+    bool operator==(const TableEntry& other) const;
+    std::string to_string() const;
 };
 
-struct Table {
-    using Entries = std::vector<TableEntry>;
-    std::string description;
-    Entries entries;
+using SelectedColumns = std::vector<TableColumnType>;
 
-    Entries::iterator begin() { return entries.begin(); }
-    Entries::const_iterator begin() const { return entries.begin(); }
-    Entries::iterator end() { return entries.end(); }
-    Entries::const_iterator end() const { return entries.end(); }
+class Table {
+public:
+    using Entries = std::vector<TableEntry>;
+
+    Entries::iterator begin() { return mEntries.begin(); }
+    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)); }
+
+    void setSelectedColumns(const SelectedColumns& s) { mSelectedColumns = s; }
+    const SelectedColumns& getSelectedColumns() const { return mSelectedColumns; }
+
+    void setDescription(std::string&& d) { mDescription = std::move(d); }
+
+    // Write table content.
+    TextTable createTextTable(bool neat = true,
+        const std::function<std::string(const std::string&)>& emitDebugInfo = nullptr) const;
+
+private:
+    std::string mDescription;
+    Entries mEntries;
+    SelectedColumns mSelectedColumns;
 };
 
 using TableEntryCompare = std::function<bool(const TableEntry &, const TableEntry &)>;
 
-enum class TableColumnType : unsigned int {
-    INTERFACE_NAME,
-    TRANSPORT,
-    SERVER_PID,
-    SERVER_ADDR,
-    CLIENT_PIDS,
-    ARCH,
-    THREADS,
+class MergedTable {
+public:
+    MergedTable(std::vector<const Table*>&& tables) : mTables(std::move(tables)) {}
+    TextTable createTextTable();
+private:
+    std::vector<const Table*> mTables;
 };
 
 enum {
diff --git a/cmds/lshal/TextTable.cpp b/cmds/lshal/TextTable.cpp
index a35917c..eca9061 100644
--- a/cmds/lshal/TextTable.cpp
+++ b/cmds/lshal/TextTable.cpp
@@ -53,5 +53,15 @@
     }
 }
 
+void TextTable::addAll(TextTable&& other) {
+    for (auto&& row : other.mTable) {
+        if (row.isRow()) {
+            computeWidth(row.fields());
+        }
+
+        mTable.emplace_back(std::move(row));
+    }
+}
+
 } // namespace lshal
 } // namespace android
diff --git a/cmds/lshal/TextTable.h b/cmds/lshal/TextTable.h
index 4636f15..91d522a 100644
--- a/cmds/lshal/TextTable.h
+++ b/cmds/lshal/TextTable.h
@@ -21,8 +21,6 @@
 #include <string>
 #include <vector>
 
-#include "TableEntry.h"
-
 namespace android {
 namespace lshal {
 
@@ -68,6 +66,8 @@
     void add(const std::string& s) { mTable.emplace_back(s); }
     void add(std::string&& s) { mTable.emplace_back(std::move(s)); }
 
+    void addAll(TextTable&& other);
+
     // Prints the table to out, with column widths adjusted appropriately according
     // to the content.
     void dump(std::ostream& out) const;
diff --git a/cmds/lshal/test.cpp b/cmds/lshal/test.cpp
index 972d508..44b196e 100644
--- a/cmds/lshal/test.cpp
+++ b/cmds/lshal/test.cpp
@@ -26,13 +26,16 @@
 #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"
 
 #define NELEMS(array)   static_cast<int>(sizeof(array) / sizeof(array[0]))
 
 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;
@@ -41,6 +44,8 @@
 using ::android::hardware::hidl_string;
 using ::android::hardware::hidl_vec;
 
+using InstanceDebugInfo = IServiceManager::InstanceDebugInfo;
+
 namespace android {
 namespace hardware {
 namespace tests {
@@ -76,7 +81,6 @@
 
 namespace lshal {
 
-
 class MockServiceManager : public IServiceManager {
 public:
     template<typename T>
@@ -107,7 +111,7 @@
 
 };
 
-class LshalTest : public ::testing::Test {
+class DebugTest : public ::testing::Test {
 public:
     void SetUp() override {
         using ::android::hardware::tests::baz::V1_0::IQuux;
@@ -122,43 +126,408 @@
                     return new Quux();
                 return nullptr;
             }));
+
+        lshal = std::make_unique<Lshal>(out, err, serviceManager, serviceManager);
     }
     void TearDown() override {}
 
     std::stringstream err;
     std::stringstream out;
     sp<MockServiceManager> serviceManager;
+
+    std::unique_ptr<Lshal> lshal;
 };
 
-TEST_F(LshalTest, Debug) {
-    const char *args[] = {
+static Arg createArg(const std::vector<const char*>& args) {
+    return Arg{static_cast<int>(args.size()), const_cast<char**>(args.data())};
+}
+
+template<typename T>
+static Status callMain(const std::unique_ptr<T>& lshal, const std::vector<const char*>& args) {
+    return lshal->main(createArg(args));
+}
+
+TEST_F(DebugTest, Debug) {
+    EXPECT_EQ(0u, callMain(lshal, {
         "lshal", "debug", "android.hardware.tests.baz@1.0::IQuux/default", "foo", "bar"
-    };
-    EXPECT_EQ(0u, Lshal(out, err, serviceManager, serviceManager)
-            .main({NELEMS(args), const_cast<char **>(args)}));
+    }));
     EXPECT_THAT(out.str(), StrEq("android.hardware.tests.baz@1.0::IQuux\nfoo\nbar"));
     EXPECT_THAT(err.str(), IsEmpty());
 }
 
-TEST_F(LshalTest, Debug2) {
-    const char *args[] = {
+TEST_F(DebugTest, Debug2) {
+    EXPECT_EQ(0u, callMain(lshal, {
         "lshal", "debug", "android.hardware.tests.baz@1.0::IQuux", "baz", "quux"
-    };
-    EXPECT_EQ(0u, Lshal(out, err, serviceManager, serviceManager)
-            .main({NELEMS(args), const_cast<char **>(args)}));
+    }));
     EXPECT_THAT(out.str(), StrEq("android.hardware.tests.baz@1.0::IQuux\nbaz\nquux"));
     EXPECT_THAT(err.str(), IsEmpty());
 }
 
-TEST_F(LshalTest, Debug3) {
-    const char *args[] = {
+TEST_F(DebugTest, Debug3) {
+    EXPECT_NE(0u, callMain(lshal, {
         "lshal", "debug", "android.hardware.tests.doesnotexist@1.0::IDoesNotExist",
-    };
-    EXPECT_NE(0u, Lshal(out, err, serviceManager, serviceManager)
-            .main({NELEMS(args), const_cast<char **>(args)}));
+    }));
     EXPECT_THAT(err.str(), HasSubstr("does not exist"));
 }
 
+class MockLshal : public Lshal {
+public:
+    MockLshal() {}
+    ~MockLshal() = default;
+    MOCK_CONST_METHOD0(out, NullableOStream<std::ostream>());
+    MOCK_CONST_METHOD0(err, NullableOStream<std::ostream>());
+};
+
+// expose protected fields and methods for ListCommand
+class MockListCommand : public ListCommand {
+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 {
+public:
+    void SetUp() override {
+        mockLshal = std::make_unique<NiceMock<MockLshal>>();
+        mockList = std::make_unique<MockListCommand>(mockLshal.get());
+        // ListCommand::parseArgs should parse arguments from the second element
+        optind = 1;
+    }
+    std::unique_ptr<MockLshal> mockLshal;
+    std::unique_ptr<MockListCommand> mockList;
+    std::stringstream output;
+};
+
+TEST_F(ListParseArgsTest, Default) {
+    // default args
+    EXPECT_EQ(0u, mockList->parseArgs(createArg({})));
+    mockList->forEachTable([](const Table& table) {
+        EXPECT_EQ(SelectedColumns({TableColumnType::INTERFACE_NAME, TableColumnType::THREADS,
+                                   TableColumnType::SERVER_PID, TableColumnType::CLIENT_PIDS}),
+                  table.getSelectedColumns());
+    });
+}
+
+TEST_F(ListParseArgsTest, Args) {
+    EXPECT_EQ(0u, mockList->parseArgs(createArg({"lshal", "-p", "-i", "-a", "-c"})));
+    mockList->forEachTable([](const Table& table) {
+        EXPECT_EQ(SelectedColumns({TableColumnType::SERVER_PID, TableColumnType::INTERFACE_NAME,
+                                   TableColumnType::SERVER_ADDR, TableColumnType::CLIENT_PIDS}),
+                  table.getSelectedColumns());
+    });
+}
+
+TEST_F(ListParseArgsTest, Cmds) {
+    EXPECT_EQ(0u, mockList->parseArgs(createArg({"lshal", "-m"})));
+    mockList->forEachTable([](const Table& table) {
+        EXPECT_EQ(SelectedColumns({TableColumnType::INTERFACE_NAME, TableColumnType::THREADS,
+                                   TableColumnType::SERVER_CMD, TableColumnType::CLIENT_CMDS}),
+                  table.getSelectedColumns());
+    });
+}
+
+TEST_F(ListParseArgsTest, DebugAndNeat) {
+    ON_CALL(*mockLshal, err()).WillByDefault(Return(NullableOStream<std::ostream>(output)));
+    EXPECT_NE(0u, mockList->parseArgs(createArg({"lshal", "--neat", "-d"})));
+    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