| /* |
| * 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 <sysutils/FrameworkCommand.h> |
| #include <sysutils/FrameworkListener.h> |
| |
| #include <poll.h> |
| #include <string.h> |
| #include <sys/socket.h> |
| #include <sys/un.h> |
| |
| #include <algorithm> |
| #include <memory> |
| |
| #include <android-base/file.h> |
| #include <android-base/logging.h> |
| #include <android-base/strings.h> |
| #include <android-base/unique_fd.h> |
| #include <cutils/sockets.h> |
| #include <gtest/gtest.h> |
| |
| 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<sockaddr*>(&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<sockaddr*>(&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<std::string> 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<TestListener>(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<TestListener> 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"); |
| } |
| |
| TEST_F(FrameworkListenerTest, MultipleClients) { |
| unique_fd client1 = clientSocket(mSocketPath); |
| unique_fd client2 = clientSocket(mSocketPath); |
| sendCmd(client1.get(), "test 1"); |
| sendCmd(client2.get(), "test 2"); |
| |
| EXPECT_EQ(std::string("42 test,2") + '\0', recvReply(client2.get())); |
| EXPECT_EQ(std::string("42 test,1") + '\0', recvReply(client1.get())); |
| } |