[libcamera-devel] [RFC PATCH 08/17] tests: IPC: Add various tests for the IPC framework

Paul Elder paul.elder at ideasonboard.com
Wed Aug 26 13:09:17 CEST 2020


This patch includes tests that I used while debugging. I just kind of
plopped them here. If there's any value in these, I'll brush them up and
keep them, otherwise I'll discard them.

- unixsocket_fds confirms that passing fds over the process boundary
  increases the fd count
- control_serialization_ipa tests de/serializing ControlList and
  ControlInfoMap using IPADataSerializer
- rpi_action_serialization tests de/serialization of RPiActionParams
- rpi_config_serialization tests de/serialization of RPiConfigureParams

Signed-off-by: Paul Elder <paul.elder at ideasonboard.com>
---
 test/ipa/meson.build                          |   4 +-
 test/ipc/meson.build                          |   3 +-
 test/ipc/unixsocket_fds.cpp                   | 284 ++++++++++++++++++
 .../control_serialization_ipa.cpp             |  85 ++++++
 test/serialization/meson.build                |   5 +-
 .../rpi_action_serialization.cpp              | 141 +++++++++
 .../rpi_config_serialization.cpp              | 162 ++++++++++
 7 files changed, 680 insertions(+), 4 deletions(-)
 create mode 100644 test/ipc/unixsocket_fds.cpp
 create mode 100644 test/serialization/control_serialization_ipa.cpp
 create mode 100644 test/serialization/rpi_action_serialization.cpp
 create mode 100644 test/serialization/rpi_config_serialization.cpp

diff --git a/test/ipa/meson.build b/test/ipa/meson.build
index ba672f3f..b89c6b66 100644
--- a/test/ipa/meson.build
+++ b/test/ipa/meson.build
@@ -2,8 +2,8 @@
 
 ipa_test = [
     ['ipa_module_test',     'ipa_module_test.cpp'],
-    ['ipa_interface_test',  'ipa_interface_test.cpp'],
-    ['ipa_wrappers_test',   'ipa_wrappers_test.cpp'],
+    #['ipa_interface_test',  'ipa_interface_test.cpp'],
+    #['ipa_wrappers_test',   'ipa_wrappers_test.cpp'],
 ]
 
 foreach t : ipa_test
diff --git a/test/ipc/meson.build b/test/ipc/meson.build
index 650df1d6..e960aa82 100644
--- a/test/ipc/meson.build
+++ b/test/ipc/meson.build
@@ -1,7 +1,8 @@
 # SPDX-License-Identifier: CC0-1.0
 
 ipc_tests = [
-    [ 'unixsocket',  'unixsocket.cpp' ],
+    [ 'unixsocket_fds', 'unixsocket_fds.cpp' ],
+    [ 'unixsocket',     'unixsocket.cpp' ],
 ]
 
 foreach t : ipc_tests
diff --git a/test/ipc/unixsocket_fds.cpp b/test/ipc/unixsocket_fds.cpp
new file mode 100644
index 00000000..8e9c6864
--- /dev/null
+++ b/test/ipc/unixsocket_fds.cpp
@@ -0,0 +1,284 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * unixsocket_fds.cpp - Unix socket IPC test with file descriptors
+ */
+
+#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/event_dispatcher.h>
+#include <libcamera/timer.h>
+
+#include "libcamera/internal/ipc_unixsocket.h"
+#include "libcamera/internal/thread.h"
+#include "libcamera/internal/utils.h"
+
+#include "test.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;
+
+class UnixSocketTestFdSlave
+{
+public:
+	UnixSocketTestFdSlave()
+		: exitCode_(EXIT_FAILURE), exit_(false)
+	{
+		dispatcher_ = Thread::current()->eventDispatcher();
+		ipc_.readyRead.connect(this, &UnixSocketTestFdSlave::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;
+		}
+
+		int fd = message.fds[0];
+
+		cerr << "slave: received " << fd << endl;
+
+		response.fds = { fd };
+
+		ret = ipc_.send(response);
+		if (ret < 0) {
+			cerr << "Reply failed" << endl;
+			stop(ret);
+		}
+	}
+
+	void stop(int code)
+	{
+		exitCode_ = code;
+		exit_ = true;
+	}
+
+	IPCUnixSocket ipc_;
+	EventDispatcher *dispatcher_;
+	int exitCode_;
+	bool exit_;
+};
+
+class UnixSocketTestFd : 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 testSendNewFd()
+	{
+		IPCUnixSocket::Payload message, response;
+		int ret;
+
+		int fd = open("/dev/null", O_RDONLY);
+		if (fd < 0) {
+			ret = -errno;
+			cerr << "Failed to open /dev/null: " << strerror(-ret) << endl;
+			return ret;
+		}
+
+		cerr << "master: opened and sending fd " << fd << endl;
+
+		message.fds = { fd };
+
+		ret = call(message, &response);
+		if (ret)
+			return ret;
+
+		cerr << "master: received fd " << response.fds[0] << endl;
+
+		return 0;
+	}
+
+	int testSendOldFd(int fd)
+	{
+		IPCUnixSocket::Payload message, response;
+		int ret;
+
+		cerr << "master: sending fd " << fd << endl;
+
+		message.fds = { fd };
+
+		ret = call(message, &response);
+		if (ret)
+			return ret;
+
+		cerr << "master: received fd " << response.fds[0] << endl;
+
+		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, &UnixSocketTestFd::readyRead);
+
+		for (int i = 0; i < 10; i++)
+			testSendNewFd();
+
+		for (int i = 0; i < 10; i++)
+			testSendOldFd(20);
+
+		/* 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;
+			}
+
+			Thread::current()->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;
+	}
+
+	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]);
+		UnixSocketTestFdSlave slave;
+		return slave.run(ipcfd);
+	}
+
+	return UnixSocketTestFd().execute();
+}
diff --git a/test/serialization/control_serialization_ipa.cpp b/test/serialization/control_serialization_ipa.cpp
new file mode 100644
index 00000000..88489326
--- /dev/null
+++ b/test/serialization/control_serialization_ipa.cpp
@@ -0,0 +1,85 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * control_serialization_ipa.cpp - Serialize and deserialize controls with IPADataSerializer
+ */
+
+#include <fcntl.h>
+#include <iostream>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <tuple>
+#include <unistd.h>
+#include <vector>
+
+#include <libcamera/ipa/raspberrypi.h>
+#include <libcamera/timer.h>
+
+#include "libcamera/internal/device_enumerator.h"
+#include "libcamera/internal/ipa_data_serializer.h"
+#include "libcamera/internal/ipa_manager.h"
+#include "libcamera/internal/ipa_module.h"
+#include "libcamera/internal/pipeline_handler.h"
+#include "libcamera/internal/thread.h"
+
+#include "serialization_test.h"
+#include "test.h"
+
+using namespace std;
+using namespace libcamera;
+
+class ControlSerializationIPATest : public CameraTest, public Test
+{
+public:
+	ControlSerializationIPATest()
+		: CameraTest("imx219")
+	{
+	}
+
+protected:
+	int init() override
+	{
+		return status_;
+	}
+
+	int run() override
+	{
+		ControlSerializer cs;
+
+		/* Create a control list with three controls. */
+		const ControlInfoMap &infoMap = camera_->controls();
+		ControlList list(infoMap);
+
+		list.set(controls::Brightness, 0.5f);
+		list.set(controls::Contrast, 1.2f);
+		list.set(controls::Saturation, 0.2f);
+
+		vector<uint8_t> infoMapBuf;
+		tie(infoMapBuf, ignore) = IPADataSerializer<const ControlInfoMap>::serialize(infoMap, &cs);
+
+		vector<uint8_t> listBuf;
+		tie(listBuf, ignore) = IPADataSerializer<ControlList>::serialize(list, *list.infoMap(), &cs);
+
+
+		const ControlInfoMap infoMapOut = IPADataSerializer<const ControlInfoMap>::deserialize(infoMapBuf, &cs);
+
+		ControlList listOut = IPADataSerializer<ControlList>::deserialize(listBuf, &cs);
+
+
+		if (!SerializationTest::equals(infoMap, infoMapOut)) {
+			cerr << "Deserialized map doesn't match original" << endl;
+			return TestFail;
+		}
+
+		if (!SerializationTest::equals(list, listOut)) {
+			cerr << "Deserialized list doesn't match original" << endl;
+			return TestFail;
+		}
+
+		return TestPass;
+	}
+};
+
+TEST_REGISTER(ControlSerializationIPATest)
diff --git a/test/serialization/meson.build b/test/serialization/meson.build
index a9d9cbcb..ecd07adf 100644
--- a/test/serialization/meson.build
+++ b/test/serialization/meson.build
@@ -1,7 +1,10 @@
 # SPDX-License-Identifier: CC0-1.0
 
 serialization_tests = [
-    [ 'control_serialization',    'control_serialization.cpp' ],
+    [ 'control_serialization_ipa', 'control_serialization_ipa.cpp' ],
+    [ 'control_serialization',     'control_serialization.cpp' ],
+    [ 'rpi_config_serialization',  'rpi_config_serialization.cpp' ],
+    [ 'rpi_action_serialization',  'rpi_action_serialization.cpp' ],
 ]
 
 foreach t : serialization_tests
diff --git a/test/serialization/rpi_action_serialization.cpp b/test/serialization/rpi_action_serialization.cpp
new file mode 100644
index 00000000..de9db03f
--- /dev/null
+++ b/test/serialization/rpi_action_serialization.cpp
@@ -0,0 +1,141 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * rpi_action_serialization.cpp - Serialize and deserialize raspberrypi IPA action data
+ */
+
+#include <fcntl.h>
+#include <iostream>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <tuple>
+#include <unistd.h>
+#include <vector>
+
+#include <libcamera/ipa/raspberrypi.h>
+#include <libcamera/ipa/raspberrypi_serializer.h>
+#include <libcamera/ipa/raspberrypi_wrapper.h>
+#include <libcamera/timer.h>
+
+#include "libcamera/internal/device_enumerator.h"
+#include "libcamera/internal/ipa_data_serializer.h"
+#include "libcamera/internal/ipa_manager.h"
+#include "libcamera/internal/ipa_module.h"
+#include "libcamera/internal/pipeline_handler.h"
+#include "libcamera/internal/thread.h"
+
+#include "serialization_test.h"
+#include "test.h"
+
+using namespace std;
+using namespace libcamera;
+
+class RPiActionSerializationTest : public CameraTest, public Test
+{
+public:
+	RPiActionSerializationTest()
+		: CameraTest("imx219")
+	{
+	}
+
+protected:
+	int init() override
+	{
+		return status_;
+	}
+
+	int run() override
+	{
+		ControlSerializer cs;
+
+		RPiConfigureParams ipaConfig;
+
+		const ControlInfoMap &infoMap1 = camera_->controls();
+		ControlList list1(infoMap1);
+		list1.set(controls::Brightness, 0.5f);
+		list1.set(controls::Contrast, 1.2f);
+		list1.set(controls::Saturation, 0.2f);
+
+		const ControlInfoMap &infoMap2 = camera_->controls();
+		ControlList list2(infoMap2);
+		list2.set(controls::Brightness, -0.8f);
+		list2.set(controls::Contrast, 2.1f);
+		list2.set(controls::Saturation, 1.1f);
+
+		RPiStatsCompletePayload s;
+		s.bufferId_ = 111;
+		s.controls_ = list2;
+
+		RPiActionParams action;
+		action.op_ = RPI_IPA_ACTION_RUN_ISP;
+		action.bufferId_ = 222;
+		action.statsComplete_ = s;
+		action.controls_ = list1;
+
+
+		vector<uint8_t> buf;
+		tie(buf, std::ignore) = IPADataSerializer<RPiActionParams>::serialize(action, &cs);
+
+		RPiActionParams actionOut = IPADataSerializer<RPiActionParams>::deserialize(buf, &cs);
+
+
+		if (!equals(action, actionOut)) {
+			cerr << "Deserialized action doesn't match original" << endl;
+			return TestFail;
+		} else {
+			cerr << "Pass!" << endl;
+		}
+
+		return TestPass;
+	}
+
+private:
+	bool equals(const RPiStatsCompletePayload &lhs, const RPiStatsCompletePayload &rhs)
+	{
+		bool matches = true;
+		if (lhs.bufferId_ != rhs.bufferId_)
+			matches = false;
+
+		if (!SerializationTest::equals(lhs.controls_, rhs.controls_)) {
+			matches = false;
+			cerr << "controls in stats complete not matching" << endl;
+		}
+
+		return matches;
+	}
+
+	bool equals(const RPiActionParams &lhs, const RPiActionParams &rhs)
+	{
+		bool matches = true;
+
+		if (lhs.op_ != rhs.op_) {
+			matches = false;
+			cerr << "op_ expected " << lhs.op_ << " got " << rhs.op_;
+		}
+
+		if (lhs.bufferId_ != rhs.bufferId_) {
+			matches = false;
+			cerr << "bufferId_ expected " << lhs.bufferId_ << " got " << rhs.bufferId_;
+		}
+
+		if (!equals(lhs.statsComplete_, rhs.statsComplete_)) {
+			matches = false;
+			cerr << "statsComplete_ expected {"
+				<< lhs.statsComplete_.bufferId_ << ", and some controls}"
+			     << " got {"
+				<< rhs.statsComplete_.bufferId_ << ", and some controls}" << endl;
+		}
+
+		if (!SerializationTest::equals(lhs.controls_, rhs.controls_)) {
+			matches = false;
+			cerr << "controls not matching" << endl;
+		}
+
+		return matches;
+	}
+
+};
+
+TEST_REGISTER(RPiActionSerializationTest)
diff --git a/test/serialization/rpi_config_serialization.cpp b/test/serialization/rpi_config_serialization.cpp
new file mode 100644
index 00000000..390579f1
--- /dev/null
+++ b/test/serialization/rpi_config_serialization.cpp
@@ -0,0 +1,162 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * rpi_config_serialization.cpp - Serialize and deserialize raspberrypi IPA configuration data
+ */
+
+#include <fcntl.h>
+#include <iostream>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <tuple>
+#include <unistd.h>
+#include <vector>
+
+#include <libcamera/ipa/raspberrypi.h>
+#include <libcamera/ipa/raspberrypi_serializer.h>
+#include <libcamera/ipa/raspberrypi_wrapper.h>
+#include <libcamera/timer.h>
+
+#include "libcamera/internal/device_enumerator.h"
+#include "libcamera/internal/ipa_data_serializer.h"
+#include "libcamera/internal/ipa_manager.h"
+#include "libcamera/internal/ipa_module.h"
+#include "libcamera/internal/pipeline_handler.h"
+#include "libcamera/internal/thread.h"
+
+#include "serialization_test.h"
+#include "test.h"
+
+using namespace std;
+using namespace libcamera;
+
+class RPiConfigSerializationTest : public CameraTest, public Test
+{
+public:
+	RPiConfigSerializationTest()
+		: CameraTest("imx219")
+	{
+	}
+
+protected:
+	int init() override
+	{
+		return status_;
+	}
+
+	int run() override
+	{
+		ControlSerializer cs;
+
+		RPiConfigureParams ipaConfig;
+
+		const ControlInfoMap &infoMap = camera_->controls();
+		ControlList list(infoMap);
+		list.set(controls::Brightness, 0.5f);
+		list.set(controls::Contrast, 1.2f);
+		list.set(controls::Saturation, 0.2f);
+
+
+		RPiConfigurePayload p1;
+		p1.op_ = RPI_IPA_CONFIG_LS_TABLE;
+		p1.lsTableHandle_ = FileDescriptor(222);
+
+		RPiConfigurePayload p2;
+		p2.op_ = RPI_IPA_CONFIG_STAGGERED_WRITE;
+		p2.staggeredWriteResult_ = RPiStaggeredWritePayload(6, 6, 6);
+
+		RPiConfigurePayload p3;
+		p3.op_ = RPI_IPA_CONFIG_SENSOR;
+		p3.controls_ = list;
+
+		ipaConfig.payload_.push_back(p1);
+		ipaConfig.payload_.push_back(p2);
+		ipaConfig.payload_.push_back(p3);
+
+		vector<uint8_t> buf;
+		vector<int32_t> fds;
+		tie(buf, fds) = IPADataSerializer<RPiConfigureParams>::serialize(ipaConfig, &cs);
+
+		RPiConfigureParams ipaConfigOut = IPADataSerializer<RPiConfigureParams>::deserialize(buf, fds, &cs);
+
+
+		if (!equals(ipaConfig, ipaConfigOut)) {
+			cerr << "Deserialized config doesn't match original" << endl;
+			return TestFail;
+		} else {
+			cerr << "Pass!" << endl;
+		}
+
+		return TestPass;
+	}
+
+private:
+	bool equals(const RPiStaggeredWritePayload &lhs, const RPiStaggeredWritePayload &rhs)
+	{
+		return lhs.gainDelay_ == rhs.gainDelay_ &&
+		       lhs.exposureDelay_ == rhs.exposureDelay_ && 
+		       lhs.sensorMetadata_ == rhs.sensorMetadata_;
+	}
+
+	bool equals(const RPiConfigurePayload &lhs, const RPiConfigurePayload &rhs)
+	{
+		bool matches = true;
+
+		if (lhs.op_ != rhs.op_) {
+			matches = false;
+			cerr << "expected " << lhs.op_ << " got " << rhs.op_;
+		}
+
+		if (lhs.lsTableHandle_.fd() != rhs.lsTableHandle_.fd()) {
+			matches = false;
+			cerr << "expected " << lhs.lsTableHandle_.fd() << " got " << rhs.lsTableHandle_.fd();
+		}
+
+		if (lhs.bufferFd_ != rhs.bufferFd_) {
+			matches = false;
+			cerr << "expected " << lhs.bufferFd_ << " got " << rhs.bufferFd_;
+		}
+
+		if (!equals(lhs.staggeredWriteResult_, rhs.staggeredWriteResult_)) {
+			matches = false;
+			cerr << "expected {"
+				<< lhs.staggeredWriteResult_.gainDelay_ << ", "
+				<< lhs.staggeredWriteResult_.exposureDelay_ << ", "
+				<< lhs.staggeredWriteResult_.sensorMetadata_ << "} "
+			     << " got {"
+				<< rhs.staggeredWriteResult_.gainDelay_ << ", "
+				<< rhs.staggeredWriteResult_.exposureDelay_ << ", "
+				<< rhs.staggeredWriteResult_.sensorMetadata_ << "} ";
+		}
+
+		if (!SerializationTest::equals(lhs.controls_, rhs.controls_)) {
+			matches = false;
+		}
+
+		return matches;
+	}
+
+	bool equals(const RPiConfigureParams &lhs, const RPiConfigureParams &rhs)
+	{
+		bool matches = true;
+
+		if (lhs.payload_.size() != rhs.payload_.size()) {
+			cerr << "non-matching size" << endl;
+			return false;
+		}
+
+		size_t len = lhs.payload_.size();
+		for (unsigned int i = 0; i < len; i++) {
+			cerr << "[" << i << "]: " << endl;
+			if (!equals(lhs.payload_[i], rhs.payload_[i]))
+				matches = false;
+		}
+
+		return matches;
+	}
+
+};
+
+TEST_REGISTER(RPiConfigSerializationTest)
-- 
2.27.0



More information about the libcamera-devel mailing list