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