[libcamera-devel,2/2] test: ipc: unix: Add test for IPCUnixSocket

Message ID 20190627020955.6166-3-niklas.soderlund@ragnatech.se
State Superseded
Headers show
Series
  • libcamera: ipc: unix: Add a IPC mechanism based on Unix sockets
Related show

Commit Message

Niklas Söderlund June 27, 2019, 2:09 a.m. UTC
Test that the IPC supports sending data and file descriptors over the
IPC medium. To be able 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 sometime respond to. A few different tests are preformed.

- 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 sends a list of file descriptors and ask the salve to calculate
  and respond with the sum of the size of the files. The master verifies
  that the calculate size is correct.

- Master send a pre-computed size and a list of file descriptors and
  ask the slave to verify that the pre-computed size matches the sum of
  the size of the file descriptors.

Signed-off-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
---
 test/ipc/meson.build    |  12 ++
 test/ipc/unixsocket.cpp | 339 ++++++++++++++++++++++++++++++++++++++++
 test/meson.build        |   1 +
 3 files changed, 352 insertions(+)
 create mode 100644 test/ipc/meson.build
 create mode 100644 test/ipc/unixsocket.cpp

Comments

Kieran Bingham June 28, 2019, 10:17 a.m. UTC | #1
Hi Niklas,

On 27/06/2019 03:09, Niklas Söderlund wrote:
> Test that the IPC supports sending data and file descriptors over the
> IPC medium. To be able 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 sometime respond to. A few different tests are preformed.
> 
> - 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 sends a list of file descriptors and ask the salve to calculate
>   and respond with the sum of the size of the files. The master verifies
>   that the calculate size is correct.
> 
> - Master send a pre-computed size and a list of file descriptors and
>   ask the slave to verify that the pre-computed size matches the sum of
>   the size of the file descriptors.
> 
> Signed-off-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
> ---
>  test/ipc/meson.build    |  12 ++
>  test/ipc/unixsocket.cpp | 339 ++++++++++++++++++++++++++++++++++++++++
>  test/meson.build        |   1 +
>  3 files changed, 352 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 0000000000000000..ca8375f35df91731
> --- /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 0000000000000000..c50d7463cee48e75
> --- /dev/null
> +++ b/test/ipc/unixsocket.cpp
> @@ -0,0 +1,339 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * Copyright (C) 2019, Google Inc.
> + *
> + * unixsocket.cpp - Unix socket IPC test
> + */
> +
> +#include <algorithm>
> +#include <atomic>
> +#include <fcntl.h>
> +#include <iostream>
> +#include <string.h>
> +#include <sys/wait.h>
> +#include <unistd.h>
> +
> +#include <libcamera/camera_manager.h>
> +#include <libcamera/event_dispatcher.h>
> +
> +#include "ipc_unixsocket.h"
> +#include "test.h"
> +
> +#define CMD_CLOSE 0
> +#define CMD_REVERSE 1
> +#define CMD_LEN_CALC 2
> +#define CMD_LEN_CMP 3
> +
> +using namespace std;
> +using namespace libcamera;
> +
> +int calcLength(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)
> +	{
> +		dispatcher_ = CameraManager::instance()->eventDispatcher();
> +		exit_.store(false, std::memory_order_release);
> +		ipc_.payloadReceived.connect(this, &UnixSocketTestSlave::payloadReceived);
> +	}
> +
> +	int run(int fd)
> +	{
> +		if (ipc_.attach(fd)) {
> +			cerr << "Failed to connect to IPC" << endl;
> +			return EXIT_FAILURE;
> +		}
> +
> +		while (!exit_.load(std::memory_order_acquire))
> +			dispatcher_->processEvents();
> +
> +		ipc_.close();
> +
> +		return exitCode_;
> +	}
> +
> +private:
> +	void payloadReceived(const IPCUnixSocket::Payload &payload)
> +	{
> +		IPCUnixSocket::Payload response;
> +		int ret;
> +
> +		const uint8_t cmd = payload.data.front();
> +
> +		cout << "Slave received command " << static_cast<unsigned int>(cmd) << endl;
> +
> +		switch (cmd) {
> +		case CMD_CLOSE:
> +			stop(0);
> +			break;
> +		case CMD_REVERSE: {
> +			std::vector<uint8_t> raw(payload.data.begin() + 1, payload.data.end());
> +			std::reverse(raw.begin(), raw.end());
> +			raw.insert(raw.begin(), cmd);
> +
> +			response.data = raw;
> +			ret = ipc_.send(response);
> +			if (ret < 0) {
> +				cerr << "Reverse fail" << endl;
> +				stop(ret);
> +			}
> +			break;
> +		}
> +		case CMD_LEN_CALC: {
> +			int size = 0;
> +			for (int fd : payload.fds)
> +				size += calcLength(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 fail" << endl;
> +				stop(ret);
> +			}
> +			break;
> +		}
> +		case CMD_LEN_CMP: {
> +			int size = 0;
> +			for (int fd : payload.fds)
> +				size += calcLength(fd);
> +
> +			int cmp;
> +			memcpy(&cmp, payload.data.data() + 1, sizeof(cmp));
> +
> +			if (cmp != size) {
> +				cerr << "Compare fail" << endl;
> +				stop(-ERANGE);
> +			}
> +			break;
> +		}
> +		default:
> +			cerr << "Unknown command " << cmd << endl;
> +			stop(-EINVAL);
> +		}
> +	}
> +
> +	void stop(int code)
> +	{
> +		exitCode_ = code;
> +		exit_.store(true, std::memory_order_release);
> +	}
> +
> +	IPCUnixSocket ipc_;
> +	EventDispatcher *dispatcher_;
> +	int exitCode_;
> +	std::atomic<bool> exit_;
> +};
> +
> +static const std::vector<std::string> testPaths = {
> +	"/proc/self/exe",
> +	"/proc/self/exe",
> +};

This looks odd - is an array of two identical strings really the best
way to make something loop twice? (I think that's the only use of this?)

Perhaps should one of those instances of /proc/self/exe be another file
in /proc/self ? comm? maps? As far as I understand it-  it just has to
be constant when read twice right ?

> +
> +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 reverse;
> +
> +		reverse.data = { CMD_REVERSE, 1, 2, 3, 4, 5 };
> +		if (ipc_.send(reverse))
> +			return TestFail;
> +
> +		CameraManager::instance()->eventDispatcher()->processEvents();
> +
> +		return 0;
> +	}
> +
> +	int testCalc()
> +	{
> +		IPCUnixSocket::Payload payload;
> +
> +		calcSize_ = 0;
> +		for (const std::string path : testPaths) {
> +			int fd = open(path.c_str(), O_RDONLY);
> +			if (fd < 0)
> +				return TestFail;
> +
> +			calcSize_ += calcLength(fd);
> +			payload.fds.push_back(fd);
> +		}
> +
> +		payload.data.push_back(CMD_LEN_CALC);
> +		if (ipc_.send(payload))
> +			return TestFail;
> +
> +		CameraManager::instance()->eventDispatcher()->processEvents();
> +
> +		return 0;
> +	}
> +
> +	int testCmp()
> +	{
> +		IPCUnixSocket::Payload payload;
> +
> +		int size = 0;
> +		for (const std::string path : testPaths) {
> +			int fd = open(path.c_str(), O_RDONLY);
> +			if (fd < 0)
> +				return TestFail;
> +
> +			size += calcLength(fd);
> +			payload.fds.push_back(fd);
> +		}
> +
> +		payload.data.resize(1 + sizeof(size));
> +		payload.data[0] = CMD_LEN_CMP;
> +		memcpy(payload.data.data() + 1, &size, sizeof(size));
> +
> +		if (ipc_.send(payload))
> +			return TestFail;
> +
> +		return 0;
> +	}
> +
> +	int run()
> +	{
> +		int slavefd = ipc_.create();
> +		if (slavefd < 0)
> +			return TestFail;
> +
> +		if (slaveStart(slavefd))
> +			return TestFail;
> +
> +		ipc_.payloadReceived.connect(this, &UnixSocketTest::payloadReceived);
> +
> +		/* Test reversing a string, this test sending only data. */
> +		ranRev_ = false;
> +		if (testReverse())
> +			return TestFail;
> +
> +		/* Test offloading a calculation, this test sending only FDs. */
> +		ranCalc_ = false;
> +		if (testCalc())
> +			return TestFail;
> +
> +		/* Test fire and forget, this tests sending data and FDs. */
> +		if (testCmp())
> +			return TestFail;
> +
> +		/* Close slave connection. */
> +		IPCUnixSocket::Payload close;
> +		close.data.push_back(CMD_CLOSE);
> +		if (ipc_.send(close))
> +			return TestFail;
> +
> +		ipc_.close();
> +		if (slaveStop())
> +			return TestFail;
> +
> +		/* Check that all tests have executed. */
> +		if (!ranRev_ || !ranCalc_) {
> +			cerr << "Not all messages responded to" << endl;
> +			return TestFail;
> +		}
> +
> +		return TestPass;
> +	}
> +
> +private:
> +	void payloadReceived(const IPCUnixSocket::Payload &payload)
> +	{
> +		const uint8_t cmd = payload.data.front();
> +		cout << "Master received command " << static_cast<unsigned int>(cmd) << endl;
> +
> +		switch (cmd) {
> +		case CMD_REVERSE: {
> +			std::vector<uint8_t> raw(payload.data.begin() + 1, payload.data.end());
> +			if (raw == std::vector<uint8_t>{ 5, 4, 3, 2, 1 })
> +				ranRev_ = true;
> +			else
> +				cerr << "Reverse response invalid" << endl;
> +
> +			break;
> +		}
> +		case CMD_LEN_CALC: {
> +			int output;
> +			memcpy(&output, payload.data.data() + 1, sizeof(output));
> +			if (output == calcSize_)
> +				ranCalc_ = true;
> +			else
> +				cerr << "Calc response invalid" << endl;
> +
> +			break;
> +		}
> +		default:
> +			cerr << "Unknown command " << cmd << endl;
> +		}
> +	}
> +
> +	pid_t pid_;
> +	IPCUnixSocket ipc_;
> +
> +	bool ranRev_;
> +	bool ranCalc_;
> +	int calcSize_;
> +};
> +
> +/*
> + * 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 c36ac24796367501..3666f6b2385bd4ca 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')
>
Niklas Söderlund June 28, 2019, 10:53 a.m. UTC | #2
Hi Kieran,

Thanks for your feedback,

On 2019-06-28 11:17:10 +0100, Kieran Bingham wrote:
> Hi Niklas,
> 
> On 27/06/2019 03:09, Niklas Söderlund wrote:
> > Test that the IPC supports sending data and file descriptors over the
> > IPC medium. To be able 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 sometime respond to. A few different tests are preformed.
> > 
> > - 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 sends a list of file descriptors and ask the salve to calculate
> >   and respond with the sum of the size of the files. The master verifies
> >   that the calculate size is correct.
> > 
> > - Master send a pre-computed size and a list of file descriptors and
> >   ask the slave to verify that the pre-computed size matches the sum of
> >   the size of the file descriptors.
> > 
> > Signed-off-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
> > ---
> >  test/ipc/meson.build    |  12 ++
> >  test/ipc/unixsocket.cpp | 339 ++++++++++++++++++++++++++++++++++++++++
> >  test/meson.build        |   1 +
> >  3 files changed, 352 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 0000000000000000..ca8375f35df91731
> > --- /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 0000000000000000..c50d7463cee48e75
> > --- /dev/null
> > +++ b/test/ipc/unixsocket.cpp
> > @@ -0,0 +1,339 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > +/*
> > + * Copyright (C) 2019, Google Inc.
> > + *
> > + * unixsocket.cpp - Unix socket IPC test
> > + */
> > +
> > +#include <algorithm>
> > +#include <atomic>
> > +#include <fcntl.h>
> > +#include <iostream>
> > +#include <string.h>
> > +#include <sys/wait.h>
> > +#include <unistd.h>
> > +
> > +#include <libcamera/camera_manager.h>
> > +#include <libcamera/event_dispatcher.h>
> > +
> > +#include "ipc_unixsocket.h"
> > +#include "test.h"
> > +
> > +#define CMD_CLOSE 0
> > +#define CMD_REVERSE 1
> > +#define CMD_LEN_CALC 2
> > +#define CMD_LEN_CMP 3
> > +
> > +using namespace std;
> > +using namespace libcamera;
> > +
> > +int calcLength(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)
> > +	{
> > +		dispatcher_ = CameraManager::instance()->eventDispatcher();
> > +		exit_.store(false, std::memory_order_release);
> > +		ipc_.payloadReceived.connect(this, &UnixSocketTestSlave::payloadReceived);
> > +	}
> > +
> > +	int run(int fd)
> > +	{
> > +		if (ipc_.attach(fd)) {
> > +			cerr << "Failed to connect to IPC" << endl;
> > +			return EXIT_FAILURE;
> > +		}
> > +
> > +		while (!exit_.load(std::memory_order_acquire))
> > +			dispatcher_->processEvents();
> > +
> > +		ipc_.close();
> > +
> > +		return exitCode_;
> > +	}
> > +
> > +private:
> > +	void payloadReceived(const IPCUnixSocket::Payload &payload)
> > +	{
> > +		IPCUnixSocket::Payload response;
> > +		int ret;
> > +
> > +		const uint8_t cmd = payload.data.front();
> > +
> > +		cout << "Slave received command " << static_cast<unsigned int>(cmd) << endl;
> > +
> > +		switch (cmd) {
> > +		case CMD_CLOSE:
> > +			stop(0);
> > +			break;
> > +		case CMD_REVERSE: {
> > +			std::vector<uint8_t> raw(payload.data.begin() + 1, payload.data.end());
> > +			std::reverse(raw.begin(), raw.end());
> > +			raw.insert(raw.begin(), cmd);
> > +
> > +			response.data = raw;
> > +			ret = ipc_.send(response);
> > +			if (ret < 0) {
> > +				cerr << "Reverse fail" << endl;
> > +				stop(ret);
> > +			}
> > +			break;
> > +		}
> > +		case CMD_LEN_CALC: {
> > +			int size = 0;
> > +			for (int fd : payload.fds)
> > +				size += calcLength(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 fail" << endl;
> > +				stop(ret);
> > +			}
> > +			break;
> > +		}
> > +		case CMD_LEN_CMP: {
> > +			int size = 0;
> > +			for (int fd : payload.fds)
> > +				size += calcLength(fd);
> > +
> > +			int cmp;
> > +			memcpy(&cmp, payload.data.data() + 1, sizeof(cmp));
> > +
> > +			if (cmp != size) {
> > +				cerr << "Compare fail" << endl;
> > +				stop(-ERANGE);
> > +			}
> > +			break;
> > +		}
> > +		default:
> > +			cerr << "Unknown command " << cmd << endl;
> > +			stop(-EINVAL);
> > +		}
> > +	}
> > +
> > +	void stop(int code)
> > +	{
> > +		exitCode_ = code;
> > +		exit_.store(true, std::memory_order_release);
> > +	}
> > +
> > +	IPCUnixSocket ipc_;
> > +	EventDispatcher *dispatcher_;
> > +	int exitCode_;
> > +	std::atomic<bool> exit_;
> > +};
> > +
> > +static const std::vector<std::string> testPaths = {
> > +	"/proc/self/exe",
> > +	"/proc/self/exe",
> > +};
> 
> This looks odd - is an array of two identical strings really the best
> way to make something loop twice? (I think that's the only use of this?)

I agree it looks odd, but it does the job :-) All that is needed is two 
file handles (or more) to test that multiple FDs can be transferred.  
Once could do with one FD but I feel its better to test with more.

> 
> Perhaps should one of those instances of /proc/self/exe be another file
> in /proc/self ? comm? maps? As far as I understand it-  it just has to
> be constant when read twice right ?

I tried finding another file in /proc/self that is a regular file and 
thus have a size. I only managed to find exe and map_files/* where the 
later provides no stable naming.

I'm open to using a different path here which would be found on every 
system, but not sure what that would be, /etc/issue maybe? I'm OK 
keeping /proc/self/exe multiple times as it does the job and at the same 
time I'm also open to suggestions of different stable paths to replace 
one of them with.

> 
> > +
> > +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 reverse;
> > +
> > +		reverse.data = { CMD_REVERSE, 1, 2, 3, 4, 5 };
> > +		if (ipc_.send(reverse))
> > +			return TestFail;
> > +
> > +		CameraManager::instance()->eventDispatcher()->processEvents();
> > +
> > +		return 0;
> > +	}
> > +
> > +	int testCalc()
> > +	{
> > +		IPCUnixSocket::Payload payload;
> > +
> > +		calcSize_ = 0;
> > +		for (const std::string path : testPaths) {
> > +			int fd = open(path.c_str(), O_RDONLY);
> > +			if (fd < 0)
> > +				return TestFail;
> > +
> > +			calcSize_ += calcLength(fd);
> > +			payload.fds.push_back(fd);
> > +		}
> > +
> > +		payload.data.push_back(CMD_LEN_CALC);
> > +		if (ipc_.send(payload))
> > +			return TestFail;
> > +
> > +		CameraManager::instance()->eventDispatcher()->processEvents();
> > +
> > +		return 0;
> > +	}
> > +
> > +	int testCmp()
> > +	{
> > +		IPCUnixSocket::Payload payload;
> > +
> > +		int size = 0;
> > +		for (const std::string path : testPaths) {
> > +			int fd = open(path.c_str(), O_RDONLY);
> > +			if (fd < 0)
> > +				return TestFail;
> > +
> > +			size += calcLength(fd);
> > +			payload.fds.push_back(fd);
> > +		}
> > +
> > +		payload.data.resize(1 + sizeof(size));
> > +		payload.data[0] = CMD_LEN_CMP;
> > +		memcpy(payload.data.data() + 1, &size, sizeof(size));
> > +
> > +		if (ipc_.send(payload))
> > +			return TestFail;
> > +
> > +		return 0;
> > +	}
> > +
> > +	int run()
> > +	{
> > +		int slavefd = ipc_.create();
> > +		if (slavefd < 0)
> > +			return TestFail;
> > +
> > +		if (slaveStart(slavefd))
> > +			return TestFail;
> > +
> > +		ipc_.payloadReceived.connect(this, &UnixSocketTest::payloadReceived);
> > +
> > +		/* Test reversing a string, this test sending only data. */
> > +		ranRev_ = false;
> > +		if (testReverse())
> > +			return TestFail;
> > +
> > +		/* Test offloading a calculation, this test sending only FDs. */
> > +		ranCalc_ = false;
> > +		if (testCalc())
> > +			return TestFail;
> > +
> > +		/* Test fire and forget, this tests sending data and FDs. */
> > +		if (testCmp())
> > +			return TestFail;
> > +
> > +		/* Close slave connection. */
> > +		IPCUnixSocket::Payload close;
> > +		close.data.push_back(CMD_CLOSE);
> > +		if (ipc_.send(close))
> > +			return TestFail;
> > +
> > +		ipc_.close();
> > +		if (slaveStop())
> > +			return TestFail;
> > +
> > +		/* Check that all tests have executed. */
> > +		if (!ranRev_ || !ranCalc_) {
> > +			cerr << "Not all messages responded to" << endl;
> > +			return TestFail;
> > +		}
> > +
> > +		return TestPass;
> > +	}
> > +
> > +private:
> > +	void payloadReceived(const IPCUnixSocket::Payload &payload)
> > +	{
> > +		const uint8_t cmd = payload.data.front();
> > +		cout << "Master received command " << static_cast<unsigned int>(cmd) << endl;
> > +
> > +		switch (cmd) {
> > +		case CMD_REVERSE: {
> > +			std::vector<uint8_t> raw(payload.data.begin() + 1, payload.data.end());
> > +			if (raw == std::vector<uint8_t>{ 5, 4, 3, 2, 1 })
> > +				ranRev_ = true;
> > +			else
> > +				cerr << "Reverse response invalid" << endl;
> > +
> > +			break;
> > +		}
> > +		case CMD_LEN_CALC: {
> > +			int output;
> > +			memcpy(&output, payload.data.data() + 1, sizeof(output));
> > +			if (output == calcSize_)
> > +				ranCalc_ = true;
> > +			else
> > +				cerr << "Calc response invalid" << endl;
> > +
> > +			break;
> > +		}
> > +		default:
> > +			cerr << "Unknown command " << cmd << endl;
> > +		}
> > +	}
> > +
> > +	pid_t pid_;
> > +	IPCUnixSocket ipc_;
> > +
> > +	bool ranRev_;
> > +	bool ranCalc_;
> > +	int calcSize_;
> > +};
> > +
> > +/*
> > + * 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 c36ac24796367501..3666f6b2385bd4ca 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
Laurent Pinchart June 28, 2019, 11:07 a.m. UTC | #3
Hi Niklas,

On Fri, Jun 28, 2019 at 12:53:14PM +0200, Niklas Söderlund wrote:
> On 2019-06-28 11:17:10 +0100, Kieran Bingham wrote:
> > On 27/06/2019 03:09, Niklas Söderlund wrote:
> >> Test that the IPC supports sending data and file descriptors over the
> >> IPC medium. To be able 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 sometime respond to. A few different tests are preformed.
> >> 
> >> - 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 sends a list of file descriptors and ask the salve to calculate
> >>   and respond with the sum of the size of the files. The master verifies
> >>   that the calculate size is correct.
> >> 
> >> - Master send a pre-computed size and a list of file descriptors and
> >>   ask the slave to verify that the pre-computed size matches the sum of
> >>   the size of the file descriptors.
> >> 
> >> Signed-off-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
> >> ---
> >>  test/ipc/meson.build    |  12 ++
> >>  test/ipc/unixsocket.cpp | 339 ++++++++++++++++++++++++++++++++++++++++
> >>  test/meson.build        |   1 +
> >>  3 files changed, 352 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 0000000000000000..ca8375f35df91731
> >> --- /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 0000000000000000..c50d7463cee48e75
> >> --- /dev/null
> >> +++ b/test/ipc/unixsocket.cpp
> >> @@ -0,0 +1,339 @@
> >> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> >> +/*
> >> + * Copyright (C) 2019, Google Inc.
> >> + *
> >> + * unixsocket.cpp - Unix socket IPC test
> >> + */
> >> +
> >> +#include <algorithm>
> >> +#include <atomic>
> >> +#include <fcntl.h>
> >> +#include <iostream>
> >> +#include <string.h>
> >> +#include <sys/wait.h>
> >> +#include <unistd.h>
> >> +
> >> +#include <libcamera/camera_manager.h>
> >> +#include <libcamera/event_dispatcher.h>
> >> +
> >> +#include "ipc_unixsocket.h"
> >> +#include "test.h"
> >> +
> >> +#define CMD_CLOSE 0
> >> +#define CMD_REVERSE 1
> >> +#define CMD_LEN_CALC 2
> >> +#define CMD_LEN_CMP 3
> >> +
> >> +using namespace std;
> >> +using namespace libcamera;
> >> +
> >> +int calcLength(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)
> >> +	{
> >> +		dispatcher_ = CameraManager::instance()->eventDispatcher();
> >> +		exit_.store(false, std::memory_order_release);
> >> +		ipc_.payloadReceived.connect(this, &UnixSocketTestSlave::payloadReceived);
> >> +	}
> >> +
> >> +	int run(int fd)
> >> +	{
> >> +		if (ipc_.attach(fd)) {
> >> +			cerr << "Failed to connect to IPC" << endl;
> >> +			return EXIT_FAILURE;
> >> +		}
> >> +
> >> +		while (!exit_.load(std::memory_order_acquire))
> >> +			dispatcher_->processEvents();
> >> +
> >> +		ipc_.close();
> >> +
> >> +		return exitCode_;
> >> +	}
> >> +
> >> +private:
> >> +	void payloadReceived(const IPCUnixSocket::Payload &payload)
> >> +	{
> >> +		IPCUnixSocket::Payload response;
> >> +		int ret;
> >> +
> >> +		const uint8_t cmd = payload.data.front();
> >> +
> >> +		cout << "Slave received command " << static_cast<unsigned int>(cmd) << endl;
> >> +
> >> +		switch (cmd) {
> >> +		case CMD_CLOSE:
> >> +			stop(0);
> >> +			break;
> >> +		case CMD_REVERSE: {
> >> +			std::vector<uint8_t> raw(payload.data.begin() + 1, payload.data.end());
> >> +			std::reverse(raw.begin(), raw.end());
> >> +			raw.insert(raw.begin(), cmd);
> >> +
> >> +			response.data = raw;
> >> +			ret = ipc_.send(response);
> >> +			if (ret < 0) {
> >> +				cerr << "Reverse fail" << endl;
> >> +				stop(ret);
> >> +			}
> >> +			break;
> >> +		}
> >> +		case CMD_LEN_CALC: {
> >> +			int size = 0;
> >> +			for (int fd : payload.fds)
> >> +				size += calcLength(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 fail" << endl;
> >> +				stop(ret);
> >> +			}
> >> +			break;
> >> +		}
> >> +		case CMD_LEN_CMP: {
> >> +			int size = 0;
> >> +			for (int fd : payload.fds)
> >> +				size += calcLength(fd);
> >> +
> >> +			int cmp;
> >> +			memcpy(&cmp, payload.data.data() + 1, sizeof(cmp));
> >> +
> >> +			if (cmp != size) {
> >> +				cerr << "Compare fail" << endl;
> >> +				stop(-ERANGE);
> >> +			}
> >> +			break;
> >> +		}
> >> +		default:
> >> +			cerr << "Unknown command " << cmd << endl;
> >> +			stop(-EINVAL);
> >> +		}
> >> +	}
> >> +
> >> +	void stop(int code)
> >> +	{
> >> +		exitCode_ = code;
> >> +		exit_.store(true, std::memory_order_release);
> >> +	}
> >> +
> >> +	IPCUnixSocket ipc_;
> >> +	EventDispatcher *dispatcher_;
> >> +	int exitCode_;
> >> +	std::atomic<bool> exit_;
> >> +};
> >> +
> >> +static const std::vector<std::string> testPaths = {
> >> +	"/proc/self/exe",
> >> +	"/proc/self/exe",
> >> +};
> > 
> > This looks odd - is an array of two identical strings really the best
> > way to make something loop twice? (I think that's the only use of this?)
> 
> I agree it looks odd, but it does the job :-) All that is needed is two 
> file handles (or more) to test that multiple FDs can be transferred.  
> Once could do with one FD but I feel its better to test with more.
> 
> > 
> > Perhaps should one of those instances of /proc/self/exe be another file
> > in /proc/self ? comm? maps? As far as I understand it-  it just has to
> > be constant when read twice right ?
> 
> I tried finding another file in /proc/self that is a regular file and 
> thus have a size. I only managed to find exe and map_files/* where the 
> later provides no stable naming.
> 
> I'm open to using a different path here which would be found on every 
> system, but not sure what that would be, /etc/issue maybe? I'm OK 
> keeping /proc/self/exe multiple times as it does the job and at the same 
> time I'm also open to suggestions of different stable paths to replace 
> one of them with.

mkstemp() ? This will have the advantage that you can write test data in
the files and read it from the other side. pipe() could also be an
option if you want two fds.

> >> +
> >> +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 reverse;
> >> +
> >> +		reverse.data = { CMD_REVERSE, 1, 2, 3, 4, 5 };
> >> +		if (ipc_.send(reverse))
> >> +			return TestFail;
> >> +
> >> +		CameraManager::instance()->eventDispatcher()->processEvents();
> >> +
> >> +		return 0;
> >> +	}
> >> +
> >> +	int testCalc()
> >> +	{
> >> +		IPCUnixSocket::Payload payload;
> >> +
> >> +		calcSize_ = 0;
> >> +		for (const std::string path : testPaths) {
> >> +			int fd = open(path.c_str(), O_RDONLY);
> >> +			if (fd < 0)
> >> +				return TestFail;
> >> +
> >> +			calcSize_ += calcLength(fd);
> >> +			payload.fds.push_back(fd);
> >> +		}
> >> +
> >> +		payload.data.push_back(CMD_LEN_CALC);
> >> +		if (ipc_.send(payload))
> >> +			return TestFail;
> >> +
> >> +		CameraManager::instance()->eventDispatcher()->processEvents();
> >> +
> >> +		return 0;
> >> +	}
> >> +
> >> +	int testCmp()
> >> +	{
> >> +		IPCUnixSocket::Payload payload;
> >> +
> >> +		int size = 0;
> >> +		for (const std::string path : testPaths) {
> >> +			int fd = open(path.c_str(), O_RDONLY);
> >> +			if (fd < 0)
> >> +				return TestFail;
> >> +
> >> +			size += calcLength(fd);
> >> +			payload.fds.push_back(fd);
> >> +		}
> >> +
> >> +		payload.data.resize(1 + sizeof(size));
> >> +		payload.data[0] = CMD_LEN_CMP;
> >> +		memcpy(payload.data.data() + 1, &size, sizeof(size));
> >> +
> >> +		if (ipc_.send(payload))
> >> +			return TestFail;
> >> +
> >> +		return 0;
> >> +	}
> >> +
> >> +	int run()
> >> +	{
> >> +		int slavefd = ipc_.create();
> >> +		if (slavefd < 0)
> >> +			return TestFail;
> >> +
> >> +		if (slaveStart(slavefd))
> >> +			return TestFail;
> >> +
> >> +		ipc_.payloadReceived.connect(this, &UnixSocketTest::payloadReceived);
> >> +
> >> +		/* Test reversing a string, this test sending only data. */
> >> +		ranRev_ = false;
> >> +		if (testReverse())
> >> +			return TestFail;
> >> +
> >> +		/* Test offloading a calculation, this test sending only FDs. */
> >> +		ranCalc_ = false;
> >> +		if (testCalc())
> >> +			return TestFail;
> >> +
> >> +		/* Test fire and forget, this tests sending data and FDs. */
> >> +		if (testCmp())
> >> +			return TestFail;
> >> +
> >> +		/* Close slave connection. */
> >> +		IPCUnixSocket::Payload close;
> >> +		close.data.push_back(CMD_CLOSE);
> >> +		if (ipc_.send(close))
> >> +			return TestFail;
> >> +
> >> +		ipc_.close();
> >> +		if (slaveStop())
> >> +			return TestFail;
> >> +
> >> +		/* Check that all tests have executed. */
> >> +		if (!ranRev_ || !ranCalc_) {
> >> +			cerr << "Not all messages responded to" << endl;
> >> +			return TestFail;
> >> +		}
> >> +
> >> +		return TestPass;
> >> +	}
> >> +
> >> +private:
> >> +	void payloadReceived(const IPCUnixSocket::Payload &payload)
> >> +	{
> >> +		const uint8_t cmd = payload.data.front();
> >> +		cout << "Master received command " << static_cast<unsigned int>(cmd) << endl;
> >> +
> >> +		switch (cmd) {
> >> +		case CMD_REVERSE: {
> >> +			std::vector<uint8_t> raw(payload.data.begin() + 1, payload.data.end());
> >> +			if (raw == std::vector<uint8_t>{ 5, 4, 3, 2, 1 })
> >> +				ranRev_ = true;
> >> +			else
> >> +				cerr << "Reverse response invalid" << endl;
> >> +
> >> +			break;
> >> +		}
> >> +		case CMD_LEN_CALC: {
> >> +			int output;
> >> +			memcpy(&output, payload.data.data() + 1, sizeof(output));
> >> +			if (output == calcSize_)
> >> +				ranCalc_ = true;
> >> +			else
> >> +				cerr << "Calc response invalid" << endl;
> >> +
> >> +			break;
> >> +		}
> >> +		default:
> >> +			cerr << "Unknown command " << cmd << endl;
> >> +		}
> >> +	}
> >> +
> >> +	pid_t pid_;
> >> +	IPCUnixSocket ipc_;
> >> +
> >> +	bool ranRev_;
> >> +	bool ranCalc_;
> >> +	int calcSize_;
> >> +};
> >> +
> >> +/*
> >> + * 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 c36ac24796367501..3666f6b2385bd4ca 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')
Laurent Pinchart June 28, 2019, 12:34 p.m. UTC | #4
Hi Niklas,

Thank you for the patch.

On Thu, Jun 27, 2019 at 04:09:55AM +0200, Niklas Söderlund wrote:
> Test that the IPC supports sending data and file descriptors over the
> IPC medium. To be able execute the test two parts are needed, one

s/To be able/To be able to/

> 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 sometime respond to. A few different tests are preformed.

s/sometime/sometimes/
s/preformed/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 sends a list of file descriptors and ask the salve to calculate

s/salve/slave/

>   and respond with the sum of the size of the files. The master verifies
>   that the calculate size is correct.

s/calculate/calculated/

> 
> - Master send a pre-computed size and a list of file descriptors and

s/send/sends/

>   ask the slave to verify that the pre-computed size matches the sum of
>   the size of the file descriptors.
> 
> Signed-off-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
> ---
>  test/ipc/meson.build    |  12 ++
>  test/ipc/unixsocket.cpp | 339 ++++++++++++++++++++++++++++++++++++++++
>  test/meson.build        |   1 +
>  3 files changed, 352 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 0000000000000000..ca8375f35df91731
> --- /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 0000000000000000..c50d7463cee48e75
> --- /dev/null
> +++ b/test/ipc/unixsocket.cpp
> @@ -0,0 +1,339 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * Copyright (C) 2019, Google Inc.
> + *
> + * unixsocket.cpp - Unix socket IPC test
> + */
> +
> +#include <algorithm>
> +#include <atomic>
> +#include <fcntl.h>
> +#include <iostream>
> +#include <string.h>
> +#include <sys/wait.h>
> +#include <unistd.h>
> +
> +#include <libcamera/camera_manager.h>
> +#include <libcamera/event_dispatcher.h>
> +
> +#include "ipc_unixsocket.h"
> +#include "test.h"
> +
> +#define CMD_CLOSE 0
> +#define CMD_REVERSE 1
> +#define CMD_LEN_CALC 2
> +#define CMD_LEN_CMP 3

s/ \([0-9]\)/\t\1/

(tab instead of space to align the numbers)

> +
> +using namespace std;
> +using namespace libcamera;
> +
> +int calcLength(int fd)

s/calcLength/calculateLength/

> +{
> +	lseek(fd, 0, 0);
> +	int size = lseek(fd, 0, SEEK_END);
> +	lseek(fd, 0, 0);
> +
> +	return size;
> +}
> +
> +class UnixSocketTestSlave
> +{
> +public:
> +	UnixSocketTestSlave()
> +		: exitCode_(EXIT_FAILURE)
> +	{
> +		dispatcher_ = CameraManager::instance()->eventDispatcher();
> +		exit_.store(false, std::memory_order_release);

With a single process you can use a bool for exit_ and assign it
directly.

> +		ipc_.payloadReceived.connect(this, &UnixSocketTestSlave::payloadReceived);
> +	}
> +
> +	int run(int fd)
> +	{
> +		if (ipc_.attach(fd)) {
> +			cerr << "Failed to connect to IPC" << endl;

s/IPC/IPC channel/

> +			return EXIT_FAILURE;
> +		}
> +
> +		while (!exit_.load(std::memory_order_acquire))
> +			dispatcher_->processEvents();
> +
> +		ipc_.close();
> +
> +		return exitCode_;
> +	}
> +
> +private:
> +	void payloadReceived(const IPCUnixSocket::Payload &payload)
> +	{
> +		IPCUnixSocket::Payload response;
> +		int ret;
> +
> +		const uint8_t cmd = payload.data.front();

data[0] would be more explicit.

> +
> +		cout << "Slave received command " << static_cast<unsigned int>(cmd) << endl;
> +
> +		switch (cmd) {
> +		case CMD_CLOSE:
> +			stop(0);
> +			break;

A blank line between each case would make it easier to read.

> +		case CMD_REVERSE: {
> +			std::vector<uint8_t> raw(payload.data.begin() + 1, payload.data.end());
> +			std::reverse(raw.begin(), raw.end());
> +			raw.insert(raw.begin(), cmd);
> +
> +			response.data = raw;

			response.data = payload.data;
			std::reverse(response.data.begin() + 1, response.data.end());

> +			ret = ipc_.send(response);
> +			if (ret < 0) {
> +				cerr << "Reverse fail" << endl;
> +				stop(ret);
> +			}
> +			break;
> +		}
> +		case CMD_LEN_CALC: {
> +			int size = 0;
> +			for (int fd : payload.fds)
> +				size += calcLength(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 fail" << endl;
> +				stop(ret);
> +			}
> +			break;
> +		}
> +		case CMD_LEN_CMP: {
> +			int size = 0;
> +			for (int fd : payload.fds)
> +				size += calcLength(fd);
> +
> +			int cmp;
> +			memcpy(&cmp, payload.data.data() + 1, sizeof(cmp));
> +
> +			if (cmp != size) {
> +				cerr << "Compare fail" << endl;
> +				stop(-ERANGE);
> +			}
> +			break;
> +		}
> +		default:
> +			cerr << "Unknown command " << cmd << endl;
> +			stop(-EINVAL);

Missing break.

> +		}
> +	}
> +
> +	void stop(int code)
> +	{
> +		exitCode_ = code;
> +		exit_.store(true, std::memory_order_release);
> +	}
> +
> +	IPCUnixSocket ipc_;
> +	EventDispatcher *dispatcher_;
> +	int exitCode_;
> +	std::atomic<bool> exit_;
> +};
> +
> +static const std::vector<std::string> testPaths = {
> +	"/proc/self/exe",
> +	"/proc/self/exe",
> +};
> +
> +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 reverse;
> +
> +		reverse.data = { CMD_REVERSE, 1, 2, 3, 4, 5 };
> +		if (ipc_.send(reverse))
> +			return TestFail;
> +
> +		CameraManager::instance()->eventDispatcher()->processEvents();

I think you should create a call() helper method that take a payload,
sends it, calls processEvents() in a loop until a response is received
(I would add a Timer to make sure we time out), and return the response.
That will simplify the logic of the rest of the tests, allowing you to
move the checks from payloadReceived() to the individual test methods.

> +
> +		return 0;
> +	}
> +
> +	int testCalc()
> +	{
> +		IPCUnixSocket::Payload payload;
> +
> +		calcSize_ = 0;
> +		for (const std::string path : testPaths) {
> +			int fd = open(path.c_str(), O_RDONLY);
> +			if (fd < 0)
> +				return TestFail;
> +
> +			calcSize_ += calcLength(fd);
> +			payload.fds.push_back(fd);
> +		}
> +
> +		payload.data.push_back(CMD_LEN_CALC);
> +		if (ipc_.send(payload))
> +			return TestFail;
> +
> +		CameraManager::instance()->eventDispatcher()->processEvents();
> +
> +		return 0;
> +	}
> +
> +	int testCmp()
> +	{
> +		IPCUnixSocket::Payload payload;
> +
> +		int size = 0;
> +		for (const std::string path : testPaths) {
> +			int fd = open(path.c_str(), O_RDONLY);
> +			if (fd < 0)
> +				return TestFail;
> +
> +			size += calcLength(fd);
> +			payload.fds.push_back(fd);
> +		}
> +
> +		payload.data.resize(1 + sizeof(size));
> +		payload.data[0] = CMD_LEN_CMP;
> +		memcpy(payload.data.data() + 1, &size, sizeof(size));
> +
> +		if (ipc_.send(payload))
> +			return TestFail;
> +
> +		return 0;
> +	}
> +
> +	int run()
> +	{
> +		int slavefd = ipc_.create();
> +		if (slavefd < 0)
> +			return TestFail;

Please print an error message when a failure is returned.

> +
> +		if (slaveStart(slavefd))
> +			return TestFail;
> +
> +		ipc_.payloadReceived.connect(this, &UnixSocketTest::payloadReceived);
> +
> +		/* Test reversing a string, this test sending only data. */
> +		ranRev_ = false;
> +		if (testReverse())
> +			return TestFail;
> +
> +		/* Test offloading a calculation, this test sending only FDs. */
> +		ranCalc_ = false;
> +		if (testCalc())
> +			return TestFail;
> +
> +		/* Test fire and forget, this tests sending data and FDs. */
> +		if (testCmp())
> +			return TestFail;
> +
> +		/* Close slave connection. */
> +		IPCUnixSocket::Payload close;
> +		close.data.push_back(CMD_CLOSE);
> +		if (ipc_.send(close))
> +			return TestFail;
> +
> +		ipc_.close();
> +		if (slaveStop())
> +			return TestFail;
> +
> +		/* Check that all tests have executed. */
> +		if (!ranRev_ || !ranCalc_) {
> +			cerr << "Not all messages responded to" << endl;
> +			return TestFail;
> +		}
> +
> +		return TestPass;
> +	}
> +
> +private:
> +	void payloadReceived(const IPCUnixSocket::Payload &payload)
> +	{
> +		const uint8_t cmd = payload.data.front();
> +		cout << "Master received command " << static_cast<unsigned int>(cmd) << endl;
> +
> +		switch (cmd) {
> +		case CMD_REVERSE: {
> +			std::vector<uint8_t> raw(payload.data.begin() + 1, payload.data.end());
> +			if (raw == std::vector<uint8_t>{ 5, 4, 3, 2, 1 })
> +				ranRev_ = true;
> +			else
> +				cerr << "Reverse response invalid" << endl;
> +
> +			break;
> +		}
> +		case CMD_LEN_CALC: {
> +			int output;
> +			memcpy(&output, payload.data.data() + 1, sizeof(output));
> +			if (output == calcSize_)
> +				ranCalc_ = true;
> +			else
> +				cerr << "Calc response invalid" << endl;
> +
> +			break;
> +		}
> +		default:
> +			cerr << "Unknown command " << cmd << endl;
> +		}
> +	}
> +
> +	pid_t pid_;
> +	IPCUnixSocket ipc_;
> +
> +	bool ranRev_;
> +	bool ranCalc_;
> +	int calcSize_;
> +};
> +
> +/*
> + * 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 c36ac24796367501..3666f6b2385bd4ca 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')

Patch

diff --git a/test/ipc/meson.build b/test/ipc/meson.build
new file mode 100644
index 0000000000000000..ca8375f35df91731
--- /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 0000000000000000..c50d7463cee48e75
--- /dev/null
+++ b/test/ipc/unixsocket.cpp
@@ -0,0 +1,339 @@ 
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * unixsocket.cpp - Unix socket IPC test
+ */
+
+#include <algorithm>
+#include <atomic>
+#include <fcntl.h>
+#include <iostream>
+#include <string.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <libcamera/camera_manager.h>
+#include <libcamera/event_dispatcher.h>
+
+#include "ipc_unixsocket.h"
+#include "test.h"
+
+#define CMD_CLOSE 0
+#define CMD_REVERSE 1
+#define CMD_LEN_CALC 2
+#define CMD_LEN_CMP 3
+
+using namespace std;
+using namespace libcamera;
+
+int calcLength(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)
+	{
+		dispatcher_ = CameraManager::instance()->eventDispatcher();
+		exit_.store(false, std::memory_order_release);
+		ipc_.payloadReceived.connect(this, &UnixSocketTestSlave::payloadReceived);
+	}
+
+	int run(int fd)
+	{
+		if (ipc_.attach(fd)) {
+			cerr << "Failed to connect to IPC" << endl;
+			return EXIT_FAILURE;
+		}
+
+		while (!exit_.load(std::memory_order_acquire))
+			dispatcher_->processEvents();
+
+		ipc_.close();
+
+		return exitCode_;
+	}
+
+private:
+	void payloadReceived(const IPCUnixSocket::Payload &payload)
+	{
+		IPCUnixSocket::Payload response;
+		int ret;
+
+		const uint8_t cmd = payload.data.front();
+
+		cout << "Slave received command " << static_cast<unsigned int>(cmd) << endl;
+
+		switch (cmd) {
+		case CMD_CLOSE:
+			stop(0);
+			break;
+		case CMD_REVERSE: {
+			std::vector<uint8_t> raw(payload.data.begin() + 1, payload.data.end());
+			std::reverse(raw.begin(), raw.end());
+			raw.insert(raw.begin(), cmd);
+
+			response.data = raw;
+			ret = ipc_.send(response);
+			if (ret < 0) {
+				cerr << "Reverse fail" << endl;
+				stop(ret);
+			}
+			break;
+		}
+		case CMD_LEN_CALC: {
+			int size = 0;
+			for (int fd : payload.fds)
+				size += calcLength(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 fail" << endl;
+				stop(ret);
+			}
+			break;
+		}
+		case CMD_LEN_CMP: {
+			int size = 0;
+			for (int fd : payload.fds)
+				size += calcLength(fd);
+
+			int cmp;
+			memcpy(&cmp, payload.data.data() + 1, sizeof(cmp));
+
+			if (cmp != size) {
+				cerr << "Compare fail" << endl;
+				stop(-ERANGE);
+			}
+			break;
+		}
+		default:
+			cerr << "Unknown command " << cmd << endl;
+			stop(-EINVAL);
+		}
+	}
+
+	void stop(int code)
+	{
+		exitCode_ = code;
+		exit_.store(true, std::memory_order_release);
+	}
+
+	IPCUnixSocket ipc_;
+	EventDispatcher *dispatcher_;
+	int exitCode_;
+	std::atomic<bool> exit_;
+};
+
+static const std::vector<std::string> testPaths = {
+	"/proc/self/exe",
+	"/proc/self/exe",
+};
+
+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 reverse;
+
+		reverse.data = { CMD_REVERSE, 1, 2, 3, 4, 5 };
+		if (ipc_.send(reverse))
+			return TestFail;
+
+		CameraManager::instance()->eventDispatcher()->processEvents();
+
+		return 0;
+	}
+
+	int testCalc()
+	{
+		IPCUnixSocket::Payload payload;
+
+		calcSize_ = 0;
+		for (const std::string path : testPaths) {
+			int fd = open(path.c_str(), O_RDONLY);
+			if (fd < 0)
+				return TestFail;
+
+			calcSize_ += calcLength(fd);
+			payload.fds.push_back(fd);
+		}
+
+		payload.data.push_back(CMD_LEN_CALC);
+		if (ipc_.send(payload))
+			return TestFail;
+
+		CameraManager::instance()->eventDispatcher()->processEvents();
+
+		return 0;
+	}
+
+	int testCmp()
+	{
+		IPCUnixSocket::Payload payload;
+
+		int size = 0;
+		for (const std::string path : testPaths) {
+			int fd = open(path.c_str(), O_RDONLY);
+			if (fd < 0)
+				return TestFail;
+
+			size += calcLength(fd);
+			payload.fds.push_back(fd);
+		}
+
+		payload.data.resize(1 + sizeof(size));
+		payload.data[0] = CMD_LEN_CMP;
+		memcpy(payload.data.data() + 1, &size, sizeof(size));
+
+		if (ipc_.send(payload))
+			return TestFail;
+
+		return 0;
+	}
+
+	int run()
+	{
+		int slavefd = ipc_.create();
+		if (slavefd < 0)
+			return TestFail;
+
+		if (slaveStart(slavefd))
+			return TestFail;
+
+		ipc_.payloadReceived.connect(this, &UnixSocketTest::payloadReceived);
+
+		/* Test reversing a string, this test sending only data. */
+		ranRev_ = false;
+		if (testReverse())
+			return TestFail;
+
+		/* Test offloading a calculation, this test sending only FDs. */
+		ranCalc_ = false;
+		if (testCalc())
+			return TestFail;
+
+		/* Test fire and forget, this tests sending data and FDs. */
+		if (testCmp())
+			return TestFail;
+
+		/* Close slave connection. */
+		IPCUnixSocket::Payload close;
+		close.data.push_back(CMD_CLOSE);
+		if (ipc_.send(close))
+			return TestFail;
+
+		ipc_.close();
+		if (slaveStop())
+			return TestFail;
+
+		/* Check that all tests have executed. */
+		if (!ranRev_ || !ranCalc_) {
+			cerr << "Not all messages responded to" << endl;
+			return TestFail;
+		}
+
+		return TestPass;
+	}
+
+private:
+	void payloadReceived(const IPCUnixSocket::Payload &payload)
+	{
+		const uint8_t cmd = payload.data.front();
+		cout << "Master received command " << static_cast<unsigned int>(cmd) << endl;
+
+		switch (cmd) {
+		case CMD_REVERSE: {
+			std::vector<uint8_t> raw(payload.data.begin() + 1, payload.data.end());
+			if (raw == std::vector<uint8_t>{ 5, 4, 3, 2, 1 })
+				ranRev_ = true;
+			else
+				cerr << "Reverse response invalid" << endl;
+
+			break;
+		}
+		case CMD_LEN_CALC: {
+			int output;
+			memcpy(&output, payload.data.data() + 1, sizeof(output));
+			if (output == calcSize_)
+				ranCalc_ = true;
+			else
+				cerr << "Calc response invalid" << endl;
+
+			break;
+		}
+		default:
+			cerr << "Unknown command " << cmd << endl;
+		}
+	}
+
+	pid_t pid_;
+	IPCUnixSocket ipc_;
+
+	bool ranRev_;
+	bool ranCalc_;
+	int calcSize_;
+};
+
+/*
+ * 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 c36ac24796367501..3666f6b2385bd4ca 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')