diff options
author | 2017-09-12 01:01:32 +0000 | |
---|---|---|
committer | 2017-09-12 01:01:32 +0000 | |
commit | 655da335700eda383b2f99c8a90c72a4dc1e0a9d (patch) | |
tree | 472d4e985713330c9a4b322f2740376f30ab8176 | |
parent | 68fff2385bad7d031188ac1466e8135f9f2e00d8 (diff) | |
parent | e1111eb11df289f44be3ba452dd50433e1439fb5 (diff) |
Merge changes from topic "lshal_pretty" am: 303322f06a
am: e1111eb11d
Change-Id: I8a053e2ad9c41fa5583cfcfcf4a2c6f8a686eb0d
-rw-r--r-- | cmds/lshal/Android.bp | 2 | ||||
-rw-r--r-- | cmds/lshal/ListCommand.cpp | 295 | ||||
-rw-r--r-- | cmds/lshal/ListCommand.h | 35 | ||||
-rw-r--r-- | cmds/lshal/Lshal.cpp | 8 | ||||
-rw-r--r-- | cmds/lshal/Lshal.h | 5 | ||||
-rw-r--r-- | cmds/lshal/TableEntry.cpp | 178 | ||||
-rw-r--r-- | cmds/lshal/TableEntry.h | 65 | ||||
-rw-r--r-- | cmds/lshal/TextTable.cpp | 10 | ||||
-rw-r--r-- | cmds/lshal/TextTable.h | 4 | ||||
-rw-r--r-- | cmds/lshal/test.cpp | 403 |
10 files changed, 770 insertions, 235 deletions
diff --git a/cmds/lshal/Android.bp b/cmds/lshal/Android.bp index 2eed1cebc2..47814688a9 100644 --- a/cmds/lshal/Android.bp +++ b/cmds/lshal/Android.bp @@ -28,6 +28,7 @@ cc_library_shared { "Lshal.cpp", "ListCommand.cpp", "PipeRelay.cpp", + "TableEntry.cpp", "TextTable.cpp", "utils.cpp", ], @@ -60,6 +61,7 @@ cc_test { "libgmock" ], shared_libs: [ + "libvintf", "android.hardware.tests.baz@1.0" ], srcs: [ diff --git a/cmds/lshal/ListCommand.cpp b/cmds/lshal/ListCommand.cpp index 31c42e7776..4550e410a0 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 @@ using ::android::hidl::manager::V1_0::IServiceManager; 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 @@ const std::string &ListCommand::getCmdline(pid_t pid) { if (pair != mCmdlines.end()) { return pair->second; } - mCmdlines[pid] = ::android::lshal::getCmdline(pid); + mCmdlines[pid] = parseCmdline(pid); return mCmdlines[pid]; } @@ -114,7 +121,7 @@ bool ListCommand::getPidInfo( 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 @@ bool ListCommand::getPidInfo( 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::postprocess() { } } } -} -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 @@ static inline bool findAndBumpVersion(vintf::ManifestHal* hal, const vintf::Vers 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 @@ void ListCommand::dumpVintf() const { 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 @@ void ListCommand::dumpVintf() const { 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 @@ void ListCommand::dumpVintf() const { 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 @@ void ListCommand::dumpVintf() const { .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 @@ static Architecture fromBaseArchitecture(::android::hidl::base::V1_0::DebugInfo: } } -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](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, + forEachTable([this, &out](const Table &table) { + + // 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 @@ void ListCommand::putEntry(TableEntrySource source, TableEntry &&entry) { 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 @@ Status ListCommand::fetchAllLibraries(const sp<IServiceManager> &manager) { } }); 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 @@ Status ListCommand::fetchPassthrough(const sp<IServiceManager> &manager) { } }); 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 @@ Status ListCommand::fetchBinderized(const sp<IServiceManager> &manager) { 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 @@ Status ListCommand::fetchBinderized(const sp<IServiceManager> &manager) { 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 @@ Status ListCommand::fetchBinderized(const sp<IServiceManager> &manager) { } 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 @@ Status ListCommand::fetchBinderized(const sp<IServiceManager> &manager) { } }); 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 @@ Status ListCommand::fetchBinderized(const sp<IServiceManager> &manager) { 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 ListCommand::fetch() { 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 @@ Status ListCommand::fetch() { 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 @@ Status ListCommand::parseArgs(const std::string &command, const Arg &arg) { { 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 @@ Status ListCommand::parseArgs(const std::string &command, const Arg &arg) { } 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 @@ Status ListCommand::parseArgs(const std::string &command, const Arg &arg) { } 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 ListCommand::main(const std::string &command, const Arg &arg) { } status = fetch(); postprocess(); - dump(); + status |= dump(); return status; } diff --git a/cmds/lshal/ListCommand.h b/cmds/lshal/ListCommand.h index 176d5b9a0a..9833d43de5 100644 --- a/cmds/lshal/ListCommand.h +++ b/cmds/lshal/ListCommand.h @@ -36,34 +36,38 @@ namespace lshal { class Lshal; +struct PidInfo { + std::map<uint64_t, Pids> refPids; // pids that are referenced + uint32_t threadUsage; // number of threads in use + uint32_t threadCount; // number of threads total +}; + class ListCommand { public: ListCommand(Lshal &lshal); + virtual ~ListCommand() = default; Status main(const std::string &command, const Arg &arg); -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 @@ private: 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 ac74775e07..da45d65e4a 100644 --- a/cmds/lshal/Lshal.cpp +++ b/cmds/lshal/Lshal.cpp @@ -109,15 +109,15 @@ void Lshal::usage(const std::string &command) const { " 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 @@ Status Lshal::parseArgs(const Arg &arg) { 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 00db5d090a..d3cc4e20a7 100644 --- a/cmds/lshal/Lshal.h +++ b/cmds/lshal/Lshal.h @@ -33,13 +33,14 @@ namespace lshal { 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 0000000000..eac0f2144a --- /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 f18f38addf..7a3b22ea67 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 @@ enum : unsigned int { }; 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 @@ struct TableEntry { 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 SelectedColumns = std::vector<TableColumnType>; + +class Table { +public: using Entries = std::vector<TableEntry>; - std::string description; - Entries entries; - 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(); } + 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 a35917c1e6..eca9061034 100644 --- a/cmds/lshal/TextTable.cpp +++ b/cmds/lshal/TextTable.cpp @@ -53,5 +53,15 @@ void TextTable::dump(std::ostream& out) const { } } +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 4636f15a27..91d522aef7 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 @@ public: 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 972d508768..44b196e11f 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_handle; using ::android::hardware::hidl_string; using ::android::hardware::hidl_vec; +using InstanceDebugInfo = IServiceManager::InstanceDebugInfo; + namespace android { namespace hardware { namespace tests { @@ -76,7 +81,6 @@ struct Quux : android::hardware::tests::baz::V1_0::IQuux { namespace lshal { - class MockServiceManager : public IServiceManager { public: template<typename T> @@ -107,7 +111,7 @@ public: }; -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 @@ public: 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 |