diff options
Diffstat (limited to 'cmds/lshal/test.cpp')
| -rw-r--r-- | cmds/lshal/test.cpp | 403 |
1 files changed, 386 insertions, 17 deletions
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 |