Merge "Use multiple patterns and emails in per-file syntax."
diff --git a/adb/daemon/remount_service.cpp b/adb/daemon/remount_service.cpp
index f7017dd..ed5f944 100644
--- a/adb/daemon/remount_service.cpp
+++ b/adb/daemon/remount_service.cpp
@@ -38,7 +38,6 @@
#include <android-base/properties.h>
#include <bootloader_message/bootloader_message.h>
#include <cutils/android_reboot.h>
-#include <ext4_utils/ext4_utils.h>
#include <fs_mgr.h>
#include <fs_mgr_overlayfs.h>
@@ -96,27 +95,6 @@
return result;
}
-static bool fs_has_shared_blocks(const std::string& mount_point, const std::string& device) {
- std::string path = mount_point + "/lost+found";
- struct statfs fs;
- if (statfs(path.c_str(), &fs) == -1 || fs.f_type != EXT4_SUPER_MAGIC) {
- return false;
- }
- unique_fd fd(unix_open(device.c_str(), O_RDONLY));
- if (fd < 0) {
- return false;
- }
- struct ext4_super_block sb;
- if (lseek64(fd, 1024, SEEK_SET) < 0 || unix_read(fd, &sb, sizeof(sb)) < 0) {
- return false;
- }
- struct fs_info info;
- if (ext4_parse_sb(&sb, &info) < 0) {
- return false;
- }
- return (info.feat_ro_compat & EXT4_FEATURE_RO_COMPAT_SHARED_BLOCKS) != 0;
-}
-
static bool can_unshare_blocks(int fd, const char* dev) {
const char* E2FSCK_BIN = "/system/bin/e2fsck";
if (access(E2FSCK_BIN, X_OK)) {
@@ -250,7 +228,7 @@
std::set<std::string> dedup;
for (const auto& partition : partitions) {
std::string dev = find_mount(partition.c_str(), partition == "/");
- if (dev.empty() || !fs_has_shared_blocks(partition, dev)) {
+ if (dev.empty() || !fs_mgr_has_shared_blocks(partition, dev)) {
continue;
}
if (can_unshare_blocks(fd.get(), dev.c_str())) {
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 20c3359..4a8ee33 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -114,28 +114,30 @@
const char* part_name;
bool optional_if_no_image;
bool optional_if_no_partition;
+ bool flashall;
bool IsSecondary() const { return nickname == nullptr; }
};
static Image images[] = {
// clang-format off
- { "boot", "boot.img", "boot.sig", "boot", false, false },
- { nullptr, "boot_other.img", "boot.sig", "boot", true, false },
- { "dtbo", "dtbo.img", "dtbo.sig", "dtbo", true, false },
- { "dts", "dt.img", "dt.sig", "dts", true, false },
- { "odm", "odm.img", "odm.sig", "odm", true, false },
- { "product", "product.img", "product.sig", "product", true, false },
+ { "boot", "boot.img", "boot.sig", "boot", false, false, true, },
+ { nullptr, "boot_other.img", "boot.sig", "boot", true, false, true, },
+ { "dtbo", "dtbo.img", "dtbo.sig", "dtbo", true, false, true, },
+ { "dts", "dt.img", "dt.sig", "dts", true, false, true, },
+ { "odm", "odm.img", "odm.sig", "odm", true, false, true, },
+ { "product", "product.img", "product.sig", "product", true, false, true, },
{ "product_services",
"product_services.img",
"product_services.sig",
"product_services",
- true, true },
- { "recovery", "recovery.img", "recovery.sig", "recovery", true, false },
- { "system", "system.img", "system.sig", "system", false, true },
- { nullptr, "system_other.img", "system.sig", "system", true, false },
- { "vbmeta", "vbmeta.img", "vbmeta.sig", "vbmeta", true, false },
- { "vendor", "vendor.img", "vendor.sig", "vendor", true, true },
- { nullptr, "vendor_other.img", "vendor.sig", "vendor", true, false },
+ true, true, true, },
+ { "recovery", "recovery.img", "recovery.sig", "recovery", true, false, true, },
+ { "system", "system.img", "system.sig", "system", false, true, true, },
+ { nullptr, "system_other.img", "system.sig", "system", true, false, true, },
+ { "vbmeta", "vbmeta.img", "vbmeta.sig", "vbmeta", true, false, true, },
+ { "vendor", "vendor.img", "vendor.sig", "vendor", true, true, true, },
+ { nullptr, "vendor_other.img", "vendor.sig", "vendor", true, false, true, },
+ { "super", "super.img", "super.sig", "super", true, true, false, },
// clang-format on
};
@@ -1242,6 +1244,7 @@
// List of partitions to flash and their slots.
std::vector<std::pair<const Image*, std::string>> entries;
for (size_t i = 0; i < arraysize(images); i++) {
+ if (!images[i].flashall) continue;
const char* slot = NULL;
if (images[i].IsSecondary()) {
if (!skip_secondary) slot = secondary.c_str();
diff --git a/init/keychords.cpp b/init/keychords.cpp
index 1af06dd..b8c1cfd 100644
--- a/init/keychords.cpp
+++ b/init/keychords.cpp
@@ -192,7 +192,7 @@
void Keychords::GeteventOpenDevice(const std::string& device) {
if (registration_.count(device)) return;
- auto fd = TEMP_FAILURE_RETRY(::open(device.c_str(), O_RDWR | O_CLOEXEC));
+ auto fd = TEMP_FAILURE_RETRY(::open(device.c_str(), O_RDONLY | O_CLOEXEC));
if (fd == -1) {
PLOG(ERROR) << "Can not open " << device;
return;
diff --git a/libsysutils/Android.bp b/libsysutils/Android.bp
index 3a12292..29d23c8 100644
--- a/libsysutils/Android.bp
+++ b/libsysutils/Android.bp
@@ -27,3 +27,16 @@
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 2137069..d9a561a 100644
--- a/libsysutils/include/sysutils/FrameworkListener.h
+++ b/libsysutils/include/sysutils/FrameworkListener.h
@@ -38,13 +38,13 @@
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/include/sysutils/SocketClient.h b/libsysutils/include/sysutils/SocketClient.h
index 1004f06..c657526 100644
--- a/libsysutils/include/sysutils/SocketClient.h
+++ b/libsysutils/include/sysutils/SocketClient.h
@@ -1,8 +1,6 @@
#ifndef _SOCKET_CLIENT_H
#define _SOCKET_CLIENT_H
-#include "List.h"
-
#include <pthread.h>
#include <cutils/atomic.h>
#include <sys/types.h>
@@ -35,7 +33,7 @@
SocketClient(int sock, bool owned, bool useCmdNum);
virtual ~SocketClient();
- int getSocket() { return mSocket; }
+ int getSocket() const { return mSocket; }
pid_t getPid() const { return mPid; }
uid_t getUid() const { return mUid; }
gid_t getGid() const { return mGid; }
@@ -84,5 +82,4 @@
int sendDataLockedv(struct iovec *iov, int iovcnt);
};
-typedef android::sysutils::List<SocketClient *> SocketClientCollection;
#endif
diff --git a/libsysutils/include/sysutils/SocketListener.h b/libsysutils/include/sysutils/SocketListener.h
index bc93b86..67a691a 100644
--- a/libsysutils/include/sysutils/SocketListener.h
+++ b/libsysutils/include/sysutils/SocketListener.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008-2014 The Android Open Source Project
+ * Copyright (C) 2008 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.
@@ -18,6 +18,8 @@
#include <pthread.h>
+#include <unordered_map>
+
#include <sysutils/SocketClient.h>
#include "SocketClientCommand.h"
@@ -25,7 +27,7 @@
bool mListen;
const char *mSocketName;
int mSock;
- SocketClientCollection *mClients;
+ std::unordered_map<int, SocketClient*> mClients;
pthread_mutex_t mClientsLock;
int mCtrlPipe[2];
pthread_t mThread;
@@ -51,8 +53,13 @@
virtual bool onDataAvailable(SocketClient *c) = 0;
private:
- bool release(SocketClient *c, bool wakeup);
static void *threadStart(void *obj);
+
+ // Add all clients to a separate list, so we don't have to hold the lock
+ // while processing it.
+ std::vector<SocketClient*> snapshotClients();
+
+ bool release(SocketClient *c, bool wakeup);
void runListener();
void init(const char *socketName, int socketFd, bool listen, bool useCmdNum);
};
diff --git a/libsysutils/src/SocketListener.cpp b/libsysutils/src/SocketListener.cpp
index 0c8a688..ded5adb 100644
--- a/libsysutils/src/SocketListener.cpp
+++ b/libsysutils/src/SocketListener.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008-2014 The Android Open Source Project
+ * Copyright (C) 2008 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.
@@ -19,13 +19,15 @@
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
-#include <sys/select.h>
+#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
+#include <vector>
+
#include <cutils/sockets.h>
#include <log/log.h>
#include <sysutils/SocketListener.h>
@@ -52,7 +54,6 @@
mSock = socketFd;
mUseCmdNum = useCmdNum;
pthread_mutex_init(&mClientsLock, nullptr);
- mClients = new SocketClientCollection();
}
SocketListener::~SocketListener() {
@@ -63,12 +64,9 @@
close(mCtrlPipe[0]);
close(mCtrlPipe[1]);
}
- SocketClientCollection::iterator it;
- for (it = mClients->begin(); it != mClients->end();) {
- (*it)->decRef();
- it = mClients->erase(it);
+ for (auto pair : mClients) {
+ pair.second->decRef();
}
- delete mClients;
}
int SocketListener::startListener() {
@@ -95,7 +93,7 @@
SLOGE("Unable to listen on socket (%s)", strerror(errno));
return -1;
} else if (!mListen)
- mClients->push_back(new SocketClient(mSock, false, mUseCmdNum));
+ mClients[mSock] = new SocketClient(mSock, false, mUseCmdNum);
if (pipe(mCtrlPipe)) {
SLOGE("pipe failed (%s)", strerror(errno));
@@ -135,11 +133,10 @@
mSock = -1;
}
- SocketClientCollection::iterator it;
- for (it = mClients->begin(); it != mClients->end();) {
- delete (*it);
- it = mClients->erase(it);
+ for (auto pair : mClients) {
+ delete pair.second;
}
+ mClients.clear();
return 0;
}
@@ -152,47 +149,30 @@
}
void SocketListener::runListener() {
-
- SocketClientCollection pendingList;
-
- while(1) {
- SocketClientCollection::iterator it;
- fd_set read_fds;
- int rc = 0;
- int max = -1;
-
- FD_ZERO(&read_fds);
-
- if (mListen) {
- max = mSock;
- FD_SET(mSock, &read_fds);
- }
-
- FD_SET(mCtrlPipe[0], &read_fds);
- if (mCtrlPipe[0] > max)
- max = mCtrlPipe[0];
+ while (true) {
+ std::vector<pollfd> fds;
pthread_mutex_lock(&mClientsLock);
- for (it = mClients->begin(); it != mClients->end(); ++it) {
+ fds.reserve(2 + mClients.size());
+ fds.push_back({.fd = mCtrlPipe[0], .events = POLLIN});
+ if (mListen) fds.push_back({.fd = mSock, .events = POLLIN});
+ for (auto pair : mClients) {
// NB: calling out to an other object with mClientsLock held (safe)
- int fd = (*it)->getSocket();
- FD_SET(fd, &read_fds);
- if (fd > max) {
- max = fd;
- }
+ const int fd = pair.second->getSocket();
+ if (fd != pair.first) SLOGE("fd mismatch: %d != %d", fd, pair.first);
+ fds.push_back({.fd = fd, .events = POLLIN});
}
pthread_mutex_unlock(&mClientsLock);
- SLOGV("mListen=%d, max=%d, mSocketName=%s", mListen, max, mSocketName);
- if ((rc = select(max + 1, &read_fds, nullptr, nullptr, nullptr)) < 0) {
- if (errno == EINTR)
- continue;
- SLOGE("select failed (%s) mListen=%d, max=%d", strerror(errno), mListen, max);
+
+ SLOGV("mListen=%d, mSocketName=%s", mListen, mSocketName);
+ int rc = TEMP_FAILURE_RETRY(poll(fds.data(), fds.size(), -1));
+ if (rc < 0) {
+ SLOGE("poll failed (%s) mListen=%d", strerror(errno), mListen);
sleep(1);
continue;
- } else if (!rc)
- continue;
+ }
- if (FD_ISSET(mCtrlPipe[0], &read_fds)) {
+ if (fds[0].revents & (POLLIN | POLLERR)) {
char c = CtrlPipe_Shutdown;
TEMP_FAILURE_RETRY(read(mCtrlPipe[0], &c, 1));
if (c == CtrlPipe_Shutdown) {
@@ -200,7 +180,7 @@
}
continue;
}
- if (mListen && FD_ISSET(mSock, &read_fds)) {
+ if (mListen && (fds[1].revents & (POLLIN | POLLERR))) {
int c = TEMP_FAILURE_RETRY(accept4(mSock, nullptr, nullptr, SOCK_CLOEXEC));
if (c < 0) {
SLOGE("accept failed (%s)", strerror(errno));
@@ -208,32 +188,33 @@
continue;
}
pthread_mutex_lock(&mClientsLock);
- mClients->push_back(new SocketClient(c, true, mUseCmdNum));
+ mClients[c] = new SocketClient(c, true, mUseCmdNum);
pthread_mutex_unlock(&mClientsLock);
}
- /* Add all active clients to the pending list first */
- pendingList.clear();
+ // Add all active clients to the pending list first, so we can release
+ // the lock before invoking the callbacks.
+ std::vector<SocketClient*> pending;
pthread_mutex_lock(&mClientsLock);
- for (it = mClients->begin(); it != mClients->end(); ++it) {
- SocketClient* c = *it;
- // NB: calling out to an other object with mClientsLock held (safe)
- int fd = c->getSocket();
- if (FD_ISSET(fd, &read_fds)) {
- pendingList.push_back(c);
+ const int size = fds.size();
+ for (int i = mListen ? 2 : 1; i < size; ++i) {
+ const struct pollfd& p = fds[i];
+ if (p.revents & (POLLIN | POLLERR)) {
+ auto it = mClients.find(p.fd);
+ if (it == mClients.end()) {
+ SLOGE("fd vanished: %d", p.fd);
+ continue;
+ }
+ SocketClient* c = it->second;
+ pending.push_back(c);
c->incRef();
}
}
pthread_mutex_unlock(&mClientsLock);
- /* Process the pending list, since it is owned by the thread,
- * there is no need to lock it */
- while (!pendingList.empty()) {
- /* Pop the first item from the list */
- it = pendingList.begin();
- SocketClient* c = *it;
- pendingList.erase(it);
- /* Process it, if false is returned, remove from list */
+ for (SocketClient* c : pending) {
+ // Process it, if false is returned, remove from the map
+ SLOGV("processing fd %d", c->getSocket());
if (!onDataAvailable(c)) {
release(c, false);
}
@@ -246,17 +227,10 @@
bool ret = false;
/* if our sockets are connection-based, remove and destroy it */
if (mListen && c) {
- /* Remove the client from our array */
+ /* Remove the client from our map */
SLOGV("going to zap %d for %s", c->getSocket(), mSocketName);
pthread_mutex_lock(&mClientsLock);
- SocketClientCollection::iterator it;
- for (it = mClients->begin(); it != mClients->end(); ++it) {
- if (*it == c) {
- mClients->erase(it);
- ret = true;
- break;
- }
- }
+ ret = (mClients.erase(c->getSocket()) != 0);
pthread_mutex_unlock(&mClientsLock);
if (ret) {
ret = c->decRef();
@@ -269,26 +243,22 @@
return ret;
}
-void SocketListener::sendBroadcast(int code, const char *msg, bool addErrno) {
- SocketClientCollection safeList;
-
- /* Add all active clients to the safe list first */
- safeList.clear();
+std::vector<SocketClient*> SocketListener::snapshotClients() {
+ std::vector<SocketClient*> clients;
pthread_mutex_lock(&mClientsLock);
- SocketClientCollection::iterator i;
-
- for (i = mClients->begin(); i != mClients->end(); ++i) {
- SocketClient* c = *i;
+ clients.reserve(mClients.size());
+ for (auto pair : mClients) {
+ SocketClient* c = pair.second;
c->incRef();
- safeList.push_back(c);
+ clients.push_back(c);
}
pthread_mutex_unlock(&mClientsLock);
- while (!safeList.empty()) {
- /* Pop the first item from the list */
- i = safeList.begin();
- SocketClient* c = *i;
- safeList.erase(i);
+ return clients;
+}
+
+void SocketListener::sendBroadcast(int code, const char *msg, bool addErrno) {
+ for (SocketClient* c : snapshotClients()) {
// broadcasts are unsolicited and should not include a cmd number
if (c->sendMsg(code, msg, addErrno, false)) {
SLOGW("Error sending broadcast (%s)", strerror(errno));
@@ -298,25 +268,7 @@
}
void SocketListener::runOnEachSocket(SocketClientCommand *command) {
- SocketClientCollection safeList;
-
- /* Add all active clients to the safe list first */
- safeList.clear();
- pthread_mutex_lock(&mClientsLock);
- SocketClientCollection::iterator i;
-
- for (i = mClients->begin(); i != mClients->end(); ++i) {
- SocketClient* c = *i;
- c->incRef();
- safeList.push_back(c);
- }
- pthread_mutex_unlock(&mClientsLock);
-
- while (!safeList.empty()) {
- /* Pop the first item from the list */
- i = safeList.begin();
- SocketClient* c = *i;
- safeList.erase(i);
+ for (SocketClient* c : snapshotClients()) {
command->runSocketCommand(c);
c->decRef();
}
diff --git a/libsysutils/src/SocketListener_test.cpp b/libsysutils/src/SocketListener_test.cpp
new file mode 100644
index 0000000..fed4546
--- /dev/null
+++ b/libsysutils/src/SocketListener_test.cpp
@@ -0,0 +1,182 @@
+/*
+ * 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");
+}
+
+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()));
+}