[libcamera-devel] [PATCH v4 2/3] test: ipc: unix: Add test for IPCUnixSocket

Kieran Bingham kieran.bingham at ideasonboard.com
Tue Jul 2 16:25:53 CEST 2019


Hi Niklas, / Laurent,

Very minor thing spotted:

On 02/07/2019 00:23, Laurent Pinchart wrote:
> From: Niklas Söderlund <niklas.soderlund at 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 at ragnatech.se>
> Reviewed-by: Laurent Pinchart <laurent.pinchart at ideasonboard.com>
> Signed-off-by: Laurent Pinchart <laurent.pinchart at 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')
> 

-- 
Regards
--
Kieran


More information about the libcamera-devel mailing list