Message ID | 20190701232339.5191-3-laurent.pinchart@ideasonboard.com |
---|---|
State | Accepted |
Commit | a00fdabacdd093c3eccb3d44155e151f59d783bf |
Headers | show |
Series |
|
Related | show |
Hi Niklas, / Laurent, Very minor thing spotted: On 02/07/2019 00:23, Laurent Pinchart wrote: > From: Niklas Söderlund <niklas.soderlund@ragnatech.se> > > Test that the IPC supports sending data and file descriptors over the > IPC medium. To be able to execute the test two parts are needed, one > to drive the test and act as the libcamera (master) and a one to act as > the IPA (slave). > > The master drives the testing posting requests to the slave to process > and sometimes respond to. A few different tests are performed. > > - Master sends an array to the slave which responds with a reversed copy > of the array. The master verifies that a reversed array is returned. > > - Master ties to sends an empty message making sure that the send call > fails. > > - Master sends a list of file descriptors and ask the slave to calculate > and respond with the sum of the size of the files. The master verifies > that the calculated size is correct. > > - Master sends a pre-computed size and a list of file descriptors and > asks the slave to verify that the pre-computed size matches the sum of > the size of the file descriptors. > > - Master sends two file descriptors and asks the salve to join the file > contents in a new file and respond with its file descriptor. The > master then verifies that the content of the returned file descriptor > matches the order of the original two files. > > Signed-off-by: Niklas Söderlund <niklas.soderlund@ragnatech.se> > Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> > --- > Changes since v3: > > - Use O_TMPFILE instead of shm_open() > - Miscellaneous typo and small fixes > --- > test/ipc/meson.build | 12 + > test/ipc/unixsocket.cpp | 502 ++++++++++++++++++++++++++++++++++++++++ > test/meson.build | 1 + > 3 files changed, 515 insertions(+) > create mode 100644 test/ipc/meson.build > create mode 100644 test/ipc/unixsocket.cpp > > diff --git a/test/ipc/meson.build b/test/ipc/meson.build > new file mode 100644 > index 000000000000..ca8375f35df9 > --- /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) Aren't IPC tests self contained, and thus suitable for is_parallel : true? (which I think is the default if is_parallel is omitted). -- Kieran > +endforeach > diff --git a/test/ipc/unixsocket.cpp b/test/ipc/unixsocket.cpp > new file mode 100644 > index 000000000000..eeef64842a75 > --- /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 c36ac2479636..3666f6b2385b 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') >
Hi Kieran, On Tue, Jul 02, 2019 at 03:25:53PM +0100, Kieran Bingham wrote: > On 02/07/2019 00:23, Laurent Pinchart wrote: > > From: Niklas Söderlund <niklas.soderlund@ragnatech.se> > > > > Test that the IPC supports sending data and file descriptors over the > > IPC medium. To be able to execute the test two parts are needed, one > > to drive the test and act as the libcamera (master) and a one to act as > > the IPA (slave). > > > > The master drives the testing posting requests to the slave to process > > and sometimes respond to. A few different tests are performed. > > > > - Master sends an array to the slave which responds with a reversed copy > > of the array. The master verifies that a reversed array is returned. > > > > - Master ties to sends an empty message making sure that the send call > > fails. > > > > - Master sends a list of file descriptors and ask the slave to calculate > > and respond with the sum of the size of the files. The master verifies > > that the calculated size is correct. > > > > - Master sends a pre-computed size and a list of file descriptors and > > asks the slave to verify that the pre-computed size matches the sum of > > the size of the file descriptors. > > > > - Master sends two file descriptors and asks the salve to join the file > > contents in a new file and respond with its file descriptor. The > > master then verifies that the content of the returned file descriptor > > matches the order of the original two files. > > > > Signed-off-by: Niklas Söderlund <niklas.soderlund@ragnatech.se> > > Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> > > --- > > Changes since v3: > > > > - Use O_TMPFILE instead of shm_open() > > - Miscellaneous typo and small fixes > > --- > > test/ipc/meson.build | 12 + > > test/ipc/unixsocket.cpp | 502 ++++++++++++++++++++++++++++++++++++++++ > > test/meson.build | 1 + > > 3 files changed, 515 insertions(+) > > create mode 100644 test/ipc/meson.build > > create mode 100644 test/ipc/unixsocket.cpp > > > > diff --git a/test/ipc/meson.build b/test/ipc/meson.build > > new file mode 100644 > > index 000000000000..ca8375f35df9 > > --- /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) > > Aren't IPC tests self contained, and thus suitable for is_parallel : > true? (which I think is the default if is_parallel is omitted). I think you're right. Niklas ? Does anyone want to submit a patch ? > > +endforeach > > diff --git a/test/ipc/unixsocket.cpp b/test/ipc/unixsocket.cpp > > new file mode 100644 > > index 000000000000..eeef64842a75 > > --- /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 c36ac2479636..3666f6b2385b 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')
On 2019-07-02 17:36:46 +0300, Laurent Pinchart wrote: > > Aren't IPC tests self contained, and thus suitable for is_parallel : > > true? (which I think is the default if is_parallel is omitted). > > I think you're right. Niklas ? Does anyone want to submit a patch ? I think both IPA, IPC and Stream tests are self-contained and could be ran in parallel, patch sent. I also had a crazy idea on how to run all tests in "parallel", send a RFC patch for this but it might be to crazy while it cuts down the runtime with 50% ;-P
diff --git a/test/ipc/meson.build b/test/ipc/meson.build new file mode 100644 index 000000000000..ca8375f35df9 --- /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 000000000000..eeef64842a75 --- /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 c36ac2479636..3666f6b2385b 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')