diff options
-rw-r--r-- | test/ipc/meson.build | 12 | ||||
-rw-r--r-- | test/ipc/unixsocket.cpp | 502 | ||||
-rw-r--r-- | test/meson.build | 1 |
3 files changed, 515 insertions, 0 deletions
diff --git a/test/ipc/meson.build b/test/ipc/meson.build new file mode 100644 index 00000000..ca8375f3 --- /dev/null +++ b/test/ipc/meson.build @@ -0,0 +1,12 @@ +ipc_tests = [ + [ 'unixsocket', 'unixsocket.cpp' ], +] + +foreach t : ipc_tests + exe = executable(t[0], t[1], + dependencies : libcamera_dep, + link_with : test_libraries, + include_directories : test_includes_internal) + + test(t[0], exe, suite : 'ipc', is_parallel : false) +endforeach diff --git a/test/ipc/unixsocket.cpp b/test/ipc/unixsocket.cpp new file mode 100644 index 00000000..eeef6484 --- /dev/null +++ b/test/ipc/unixsocket.cpp @@ -0,0 +1,502 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * unixsocket.cpp - Unix socket IPC test + */ + +#include <algorithm> +#include <fcntl.h> +#include <iostream> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <libcamera/camera_manager.h> +#include <libcamera/event_dispatcher.h> +#include <libcamera/timer.h> + +#include "ipc_unixsocket.h" +#include "test.h" +#include "utils.h" + +#define CMD_CLOSE 0 +#define CMD_REVERSE 1 +#define CMD_LEN_CALC 2 +#define CMD_LEN_CMP 3 +#define CMD_JOIN 4 + +using namespace std; +using namespace libcamera; + +int calculateLength(int fd) +{ + lseek(fd, 0, 0); + int size = lseek(fd, 0, SEEK_END); + lseek(fd, 0, 0); + + return size; +} + +class UnixSocketTestSlave +{ +public: + UnixSocketTestSlave() + : exitCode_(EXIT_FAILURE), exit_(false) + { + dispatcher_ = CameraManager::instance()->eventDispatcher(); + ipc_.readyRead.connect(this, &UnixSocketTestSlave::readyRead); + } + + int run(int fd) + { + if (ipc_.bind(fd)) { + cerr << "Failed to connect to IPC channel" << endl; + return EXIT_FAILURE; + } + + while (!exit_) + dispatcher_->processEvents(); + + ipc_.close(); + + return exitCode_; + } + +private: + void readyRead(IPCUnixSocket *ipc) + { + IPCUnixSocket::Payload message, response; + int ret; + + ret = ipc->receive(&message); + if (ret) { + cerr << "Receive message failed: " << ret << endl; + return; + } + + const uint8_t cmd = message.data[0]; + + switch (cmd) { + case CMD_CLOSE: + stop(0); + break; + + case CMD_REVERSE: { + response.data = message.data; + std::reverse(response.data.begin() + 1, response.data.end()); + + ret = ipc_.send(response); + if (ret < 0) { + cerr << "Reverse failed" << endl; + stop(ret); + } + break; + } + + case CMD_LEN_CALC: { + int size = 0; + for (int fd : message.fds) + size += calculateLength(fd); + + response.data.resize(1 + sizeof(size)); + response.data[0] = cmd; + memcpy(response.data.data() + 1, &size, sizeof(size)); + + ret = ipc_.send(response); + if (ret < 0) { + cerr << "Calc failed" << endl; + stop(ret); + } + break; + } + + case CMD_LEN_CMP: { + int size = 0; + for (int fd : message.fds) + size += calculateLength(fd); + + int cmp; + memcpy(&cmp, message.data.data() + 1, sizeof(cmp)); + + if (cmp != size) { + cerr << "Compare failed" << endl; + stop(-ERANGE); + } + break; + } + + case CMD_JOIN: { + int outfd = open("/tmp", O_TMPFILE | O_RDWR, + S_IRUSR | S_IWUSR); + if (outfd < 0) { + cerr << "Create out file failed" << endl; + stop(outfd); + return; + } + + for (int fd : message.fds) { + while (true) { + char buf[32]; + ssize_t num = read(fd, &buf, sizeof(buf)); + + if (num < 0) { + cerr << "Read failed" << endl; + stop(-EIO); + return; + } else if (!num) + break; + + if (write(outfd, buf, num) < 0) { + cerr << "Write failed" << endl; + stop(-EIO); + return; + } + } + + close(fd); + } + + lseek(outfd, 0, 0); + response.data.push_back(CMD_JOIN); + response.fds.push_back(outfd); + + ret = ipc_.send(response); + if (ret < 0) { + cerr << "Join failed" << endl; + stop(ret); + } + + close(outfd); + + break; + } + + default: + cerr << "Unknown command " << cmd << endl; + stop(-EINVAL); + break; + } + } + + void stop(int code) + { + exitCode_ = code; + exit_ = true; + } + + IPCUnixSocket ipc_; + EventDispatcher *dispatcher_; + int exitCode_; + bool exit_; +}; + +class UnixSocketTest : public Test +{ +protected: + int slaveStart(int fd) + { + pid_ = fork(); + + if (pid_ == -1) + return TestFail; + + if (!pid_) { + std::string arg = std::to_string(fd); + execl("/proc/self/exe", "/proc/self/exe", + arg.c_str(), nullptr); + + /* Only get here if exec fails. */ + exit(TestFail); + } + + return TestPass; + } + + int slaveStop() + { + int status; + + if (pid_ < 0) + return TestFail; + + if (waitpid(pid_, &status, 0) < 0) + return TestFail; + + if (!WIFEXITED(status) || WEXITSTATUS(status)) + return TestFail; + + return TestPass; + } + + int testReverse() + { + IPCUnixSocket::Payload message, response; + int ret; + + message.data = { CMD_REVERSE, 1, 2, 3, 4, 5 }; + + ret = call(message, &response); + if (ret) + return ret; + + std::reverse(response.data.begin() + 1, response.data.end()); + if (message.data != response.data) + return TestFail; + + return 0; + } + + int testEmptyFail() + { + IPCUnixSocket::Payload message; + + return ipc_.send(message) != -EINVAL; + } + + int testCalc() + { + IPCUnixSocket::Payload message, response; + int sizeOut, sizeIn, ret; + + sizeOut = prepareFDs(&message, 2); + if (sizeOut < 0) + return sizeOut; + + message.data.push_back(CMD_LEN_CALC); + + ret = call(message, &response); + if (ret) + return ret; + + memcpy(&sizeIn, response.data.data() + 1, sizeof(sizeIn)); + if (sizeOut != sizeIn) + return TestFail; + + return 0; + } + + int testCmp() + { + IPCUnixSocket::Payload message; + int size; + + size = prepareFDs(&message, 7); + if (size < 0) + return size; + + message.data.resize(1 + sizeof(size)); + message.data[0] = CMD_LEN_CMP; + memcpy(message.data.data() + 1, &size, sizeof(size)); + + if (ipc_.send(message)) + return TestFail; + + return 0; + } + + int testFdOrder() + { + IPCUnixSocket::Payload message, response; + int ret; + + static const char *strings[2] = { + "Foo", + "Bar", + }; + int fds[2]; + + for (unsigned int i = 0; i < ARRAY_SIZE(strings); i++) { + unsigned int len = strlen(strings[i]); + + fds[i] = open("/tmp", O_TMPFILE | O_RDWR, + S_IRUSR | S_IWUSR); + if (fds[i] < 0) + return TestFail; + + ret = write(fds[i], strings[i], len); + if (ret < 0) + return TestFail; + + lseek(fds[i], 0, 0); + message.fds.push_back(fds[i]); + } + + message.data.push_back(CMD_JOIN); + + ret = call(message, &response); + if (ret) + return ret; + + for (unsigned int i = 0; i < ARRAY_SIZE(strings); i++) { + unsigned int len = strlen(strings[i]); + char buf[len]; + + close(fds[i]); + + if (read(response.fds[0], &buf, len) <= 0) + return TestFail; + + if (memcmp(buf, strings[i], len)) + return TestFail; + } + + close(response.fds[0]); + + return 0; + } + + int init() + { + callResponse_ = nullptr; + return 0; + } + + int run() + { + int slavefd = ipc_.create(); + if (slavefd < 0) + return TestFail; + + if (slaveStart(slavefd)) { + cerr << "Failed to start slave" << endl; + return TestFail; + } + + ipc_.readyRead.connect(this, &UnixSocketTest::readyRead); + + /* Test reversing a string, this test sending only data. */ + if (testReverse()) { + cerr << "Reveres array test failed" << endl; + return TestFail; + } + + /* Test that an empty message fails. */ + if (testEmptyFail()) { + cerr << "Empty message test failed" << endl; + return TestFail; + } + + /* Test offloading a calculation, this test sending only FDs. */ + if (testCalc()) { + cerr << "Calc test failed" << endl; + return TestFail; + } + + /* Test fire and forget, this tests sending data and FDs. */ + if (testCmp()) { + cerr << "Cmp test failed" << endl; + return TestFail; + } + + /* Test order of file descriptors. */ + if (testFdOrder()) { + cerr << "fd order test failed" << endl; + return TestFail; + } + + /* Close slave connection. */ + IPCUnixSocket::Payload close; + close.data.push_back(CMD_CLOSE); + if (ipc_.send(close)) { + cerr << "Closing IPC channel failed" << endl; + return TestFail; + } + + ipc_.close(); + if (slaveStop()) { + cerr << "Failed to stop slave" << endl; + return TestFail; + } + + return TestPass; + } + +private: + int call(const IPCUnixSocket::Payload &message, IPCUnixSocket::Payload *response) + { + Timer timeout; + int ret; + + callDone_ = false; + callResponse_ = response; + + ret = ipc_.send(message); + if (ret) + return ret; + + timeout.start(200); + while (!callDone_) { + if (!timeout.isRunning()) { + cerr << "Call timeout!" << endl; + callResponse_ = nullptr; + return -ETIMEDOUT; + } + + CameraManager::instance()->eventDispatcher()->processEvents(); + } + + callResponse_ = nullptr; + + return 0; + } + + void readyRead(IPCUnixSocket *ipc) + { + if (!callResponse_) { + cerr << "Read ready without expecting data, fail." << endl; + return; + } + + if (ipc->receive(callResponse_)) { + cerr << "Receive message failed" << endl; + return; + } + + callDone_ = true; + } + + int prepareFDs(IPCUnixSocket::Payload *message, unsigned int num) + { + int fd = open("/proc/self/exe", O_RDONLY); + if (fd < 0) + return fd; + + int size = 0; + for (unsigned int i = 0; i < num; i++) { + int clone = dup(fd); + if (clone < 0) + return clone; + + size += calculateLength(clone); + message->fds.push_back(clone); + } + + close(fd); + + return size; + } + + pid_t pid_; + IPCUnixSocket ipc_; + bool callDone_; + IPCUnixSocket::Payload *callResponse_; +}; + +/* + * Can't use TEST_REGISTER() as single binary needs to act as both proxy + * master and slave. + */ +int main(int argc, char **argv) +{ + if (argc == 2) { + int ipcfd = std::stoi(argv[1]); + UnixSocketTestSlave slave; + return slave.run(ipcfd); + } + + return UnixSocketTest().execute(); +} diff --git a/test/meson.build b/test/meson.build index c36ac247..3666f6b2 100644 --- a/test/meson.build +++ b/test/meson.build @@ -2,6 +2,7 @@ subdir('libtest') subdir('camera') subdir('ipa') +subdir('ipc') subdir('media_device') subdir('pipeline') subdir('stream') |