summaryrefslogtreecommitdiff
path: root/libsysutils
diff options
context:
space:
mode:
authorBernie Innocenti <codewiz@google.com>2018-06-05 22:32:34 +0900
committerBernie Innocenti <codewiz@google.com>2018-08-28 05:52:48 +0900
commit763ccaa104e481614a25af05c4a639d4c4c089f3 (patch)
treebf07da86228431acf3e9cd8a3ee6f216d7e2baab /libsysutils
parent572d33347baac6fbcda09a2e66e38a1bd969cf7c (diff)
downloadcore-763ccaa104e481614a25af05c4a639d4c4c089f3.tar.gz
libsysutils: add some rudimentary tests
Test: atest libsysutils_tests Change-Id: I3aa44c5af821c3161cf598b86216480d2424d336
Diffstat (limited to 'libsysutils')
-rw-r--r--libsysutils/Android.bp13
-rw-r--r--libsysutils/include/sysutils/FrameworkListener.h8
-rw-r--r--libsysutils/src/SocketListener.cpp1
-rw-r--r--libsysutils/src/SocketListener_test.cpp172
4 files changed, 189 insertions, 5 deletions
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 <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");
+}