diff --git a/libsysutils/Android.bp b/libsysutils/Android.bp index 3a1229228..29d23c832 100644 --- a/libsysutils/Android.bp +++ b/libsysutils/Android.bp @@ -27,3 +27,16 @@ cc_library_shared { export_include_dirs: ["include"], } + +cc_test { + name: "libsysutils_tests", + test_suites: ["device-tests"], + srcs: [ + "src/SocketListener_test.cpp", + ], + shared_libs: [ + "libbase", + "libcutils", + "libsysutils", + ], +} diff --git a/libsysutils/include/sysutils/FrameworkListener.h b/libsysutils/include/sysutils/FrameworkListener.h index 2137069fb..d9a561a0d 100644 --- a/libsysutils/include/sysutils/FrameworkListener.h +++ b/libsysutils/include/sysutils/FrameworkListener.h @@ -38,13 +38,13 @@ public: FrameworkListener(const char *socketName); FrameworkListener(const char *socketName, bool withSeq); FrameworkListener(int sock); - virtual ~FrameworkListener() {} + ~FrameworkListener() override {} -protected: + protected: void registerCmd(FrameworkCommand *cmd); - virtual bool onDataAvailable(SocketClient *c); + bool onDataAvailable(SocketClient* c) override; -private: + private: void dispatchCommand(SocketClient *c, char *data); void init(const char *socketName, bool withSeq); }; diff --git a/libsysutils/src/SocketListener.cpp b/libsysutils/src/SocketListener.cpp index 0c8a6881c..6b3f79659 100644 --- a/libsysutils/src/SocketListener.cpp +++ b/libsysutils/src/SocketListener.cpp @@ -52,7 +52,6 @@ void SocketListener::init(const char *socketName, int socketFd, bool listen, boo mSock = socketFd; mUseCmdNum = useCmdNum; pthread_mutex_init(&mClientsLock, nullptr); - mClients = new SocketClientCollection(); } SocketListener::~SocketListener() { diff --git a/libsysutils/src/SocketListener_test.cpp b/libsysutils/src/SocketListener_test.cpp new file mode 100644 index 000000000..aa59e293a --- /dev/null +++ b/libsysutils/src/SocketListener_test.cpp @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2018 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. + */ + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +using android::base::unique_fd; + +namespace { + +std::string testSocketPath() { + const testing::TestInfo* const test_info = + testing::UnitTest::GetInstance()->current_test_info(); + return std::string(ANDROID_SOCKET_DIR "/") + std::string(test_info->test_case_name()) + + std::string(".") + std::string(test_info->name()); +} + +unique_fd serverSocket(const std::string& path) { + unlink(path.c_str()); + + unique_fd fd(socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)); + EXPECT_GE(fd.get(), 0); + + struct sockaddr_un addr = {.sun_family = AF_UNIX}; + strlcpy(addr.sun_path, path.c_str(), sizeof(addr.sun_path)); + + EXPECT_EQ(bind(fd.get(), reinterpret_cast(&addr), sizeof(addr)), 0) + << "bind() to " << path << " failed: " << strerror(errno); + EXPECT_EQ(android_get_control_socket(path.c_str()), -1); + + return fd; +} + +unique_fd clientSocket(const std::string& path) { + unique_fd fd(socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)); + EXPECT_GE(fd.get(), 0); + + struct sockaddr_un addr = {.sun_family = AF_UNIX}; + strlcpy(addr.sun_path, path.c_str(), sizeof(addr.sun_path)); + + EXPECT_EQ(0, connect(fd.get(), reinterpret_cast(&addr), sizeof(addr))) + << "connect() to " << path << " failed: " << strerror(errno); + + return fd; +} + +void sendCmd(int fd, const char* cmd) { + EXPECT_TRUE(android::base::WriteFully(fd, cmd, strlen(cmd) + 1)) + << "write() to socket failed: " << strerror(errno); +} + +std::string recvReply(int fd) { + pollfd fds = {.fd = fd, .events = POLLIN}; + int poll_events = poll(&fds, 1, -1); + EXPECT_EQ(1, poll_events); + + // Technically, this one-shot read() is incorrect: we should keep on + // reading the socket until we get a \0. But this is also how + // FrameworkListener::onDataAvailable() reads, and it works because + // replies are always send with a single write() call, and clients + // always read replies before queueing the next command. + char buf[1024]; + ssize_t len = read(fd, buf, sizeof(buf)); + EXPECT_GE(len, 0) << "read() from socket failed: " << strerror(errno); + return len > 0 ? std::string(buf, buf + len) : ""; +} + +// Test command which echoes back all its arguments as a comma-separated list. +// Always returns error code 42 +// +// TODO: enable testing replies with addErrno=true and useCmdNum=true +class TestCommand : public FrameworkCommand { + public: + TestCommand() : FrameworkCommand("test") {} + ~TestCommand() override {} + + int runCommand(SocketClient* cli, int argc, char** argv) { + std::vector args(argv, argv + argc); + std::string reply = android::base::Join(args, ','); + cli->sendMsg(42, reply.c_str(), /*addErrno=*/false, /*useCmdNum=*/false); + return 0; + } +}; + +// A test listener with a single command. +class TestListener : public FrameworkListener { + public: + TestListener(int fd) : FrameworkListener(fd) { + registerCmd(new TestCommand); // Leaked :-( + } +}; + +} // unnamed namespace + +class FrameworkListenerTest : public testing::Test { + public: + FrameworkListenerTest() { + mSocketPath = testSocketPath(); + mSserverFd = serverSocket(mSocketPath); + mListener = std::make_unique(mSserverFd.get()); + EXPECT_EQ(0, mListener->startListener()); + } + + ~FrameworkListenerTest() override { + EXPECT_EQ(0, mListener->stopListener()); + + // Wouldn't it be cool if unique_fd had an option for taking care of this? + unlink(mSocketPath.c_str()); + } + + void testCommand(const char* command, const char* expected) { + unique_fd client_fd = clientSocket(mSocketPath); + sendCmd(client_fd.get(), command); + + std::string reply = recvReply(client_fd.get()); + EXPECT_EQ(std::string(expected) + '\0', reply); + } + + protected: + std::string mSocketPath; + unique_fd mSserverFd; + std::unique_ptr mListener; +}; + +TEST_F(FrameworkListenerTest, DoesNothing) { + // Let the test harness start and stop a FrameworkListener + // without sending any commands through it. +} + +TEST_F(FrameworkListenerTest, DispatchesValidCommands) { + testCommand("test", "42 test"); + testCommand("test arg1 arg2", "42 test,arg1,arg2"); + testCommand("test \"arg1 still_arg1\" arg2", "42 test,arg1 still_arg1,arg2"); + testCommand("test \"escaped quote: '\\\"'\"", "42 test,escaped quote: '\"'"); + + // Perhaps this behavior was unintended, but would be good to detect any + // changes, in case anyone depends on it. + testCommand("test ", "42 test,,,"); +} + +TEST_F(FrameworkListenerTest, RejectsInvalidCommands) { + testCommand("unknown arg1 arg2", "500 Command not recognized"); + testCommand("test \"arg1 arg2", "500 Unclosed quotes error"); + testCommand("test \\a", "500 Unsupported escape sequence"); +}