[libcamera-devel,v2,23/24] test: ipa: Add IPA wrappers test

Message ID 20191108205409.18845-24-laurent.pinchart@ideasonboard.com
State Accepted
Headers show
Series
  • Control serialization and IPA C API
Related show

Commit Message

Laurent Pinchart Nov. 8, 2019, 8:54 p.m. UTC
Wrap an IPAInterface in an IPAInterfaceWrapper in an IPAContextWrapper,
and verify that the translation between C and C++ APIs work correctly.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 test/ipa/ipa_wrappers_test.cpp | 390 +++++++++++++++++++++++++++++++++
 test/ipa/meson.build           |   5 +-
 2 files changed, 393 insertions(+), 2 deletions(-)
 create mode 100644 test/ipa/ipa_wrappers_test.cpp

Comments

Niklas Söderlund Nov. 18, 2019, 11:28 p.m. UTC | #1
Hi Laurent,

Thanks for your patch.

On 2019-11-08 22:54:08 +0200, Laurent Pinchart wrote:
> Wrap an IPAInterface in an IPAInterfaceWrapper in an IPAContextWrapper,
> and verify that the translation between C and C++ APIs work correctly.
> 
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>

> ---
>  test/ipa/ipa_wrappers_test.cpp | 390 +++++++++++++++++++++++++++++++++
>  test/ipa/meson.build           |   5 +-
>  2 files changed, 393 insertions(+), 2 deletions(-)
>  create mode 100644 test/ipa/ipa_wrappers_test.cpp
> 
> diff --git a/test/ipa/ipa_wrappers_test.cpp b/test/ipa/ipa_wrappers_test.cpp
> new file mode 100644
> index 000000000000..c0717f0e301b
> --- /dev/null
> +++ b/test/ipa/ipa_wrappers_test.cpp
> @@ -0,0 +1,390 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * Copyright (C) 2019, Google Inc.
> + *
> + * ipa_wrappers_test.cpp - Test the IPA interface and context wrappers
> + */
> +
> +#include <fcntl.h>
> +#include <iostream>
> +#include <memory>
> +#include <linux/videodev2.h>
> +#include <sys/stat.h>
> +#include <unistd.h>
> +
> +#include <libcamera/controls.h>
> +#include <libipa/ipa_interface_wrapper.h>
> +
> +#include "device_enumerator.h"
> +#include "ipa_context_wrapper.h"
> +#include "media_device.h"
> +#include "utils.h"
> +#include "v4l2_subdevice.h"
> +
> +#include "test.h"
> +
> +using namespace libcamera;
> +using namespace std;
> +
> +enum Operation {
> +	Op_init,
> +	Op_configure,
> +	Op_mapBuffers,
> +	Op_unmapBuffers,
> +	Op_processEvent,
> +};
> +
> +class TestIPAInterface : public IPAInterface
> +{
> +public:
> +	TestIPAInterface()
> +		: sequence_(0)
> +	{
> +	}
> +
> +	int init() override
> +	{
> +		report(Op_init, TestPass);
> +		return 0;
> +	}
> +
> +	void configure(const std::map<unsigned int, IPAStream> &streamConfig,
> +		       const std::map<unsigned int, const ControlInfoMap &> &entityControls) override
> +	{
> +		/* Verify streamConfig. */
> +		if (streamConfig.size() != 2) {
> +			cerr << "configure(): Invalid number of streams "
> +			     << streamConfig.size() << endl;
> +			return report(Op_configure, TestFail);
> +		}
> +
> +		auto iter = streamConfig.find(1);
> +		if (iter == streamConfig.end()) {
> +			cerr << "configure(): No configuration for stream 1" << endl;
> +			return report(Op_configure, TestFail);
> +		}
> +		const IPAStream *stream = &iter->second;
> +		if (stream->pixelFormat != V4L2_PIX_FMT_YUYV ||
> +		    stream->size != Size{ 1024, 768 }) {
> +			cerr << "configure(): Invalid configuration for stream 1" << endl;
> +			return report(Op_configure, TestFail);
> +		}
> +
> +		iter = streamConfig.find(2);
> +		if (iter == streamConfig.end()) {
> +			cerr << "configure(): No configuration for stream 2" << endl;
> +			return report(Op_configure, TestFail);
> +		}
> +		stream = &iter->second;
> +		if (stream->pixelFormat != V4L2_PIX_FMT_NV12 ||
> +		    stream->size != Size{ 800, 600 }) {
> +			cerr << "configure(): Invalid configuration for stream 2" << endl;
> +			return report(Op_configure, TestFail);
> +		}
> +
> +		/* Verify entityControls. */
> +		auto ctrlIter = entityControls.find(42);
> +		if (ctrlIter == entityControls.end()) {
> +			cerr << "configure(): Controls not found" << endl;
> +			return report(Op_configure, TestFail);
> +		}
> +
> +		const ControlInfoMap &infoMap = ctrlIter->second;
> +
> +		if (infoMap.count(V4L2_CID_BRIGHTNESS) != 1 ||
> +		    infoMap.count(V4L2_CID_CONTRAST) != 1 ||
> +		    infoMap.count(V4L2_CID_SATURATION) != 1) {
> +			cerr << "configure(): Invalid control IDs" << endl;
> +			return report(Op_configure, TestFail);
> +		}
> +
> +		report(Op_configure, TestPass);
> +	}
> +
> +	void mapBuffers(const std::vector<IPABuffer> &buffers) override
> +	{
> +		if (buffers.size() != 2) {
> +			cerr << "mapBuffers(): Invalid number of buffers "
> +			     << buffers.size() << endl;
> +			return report(Op_mapBuffers, TestFail);
> +		}
> +
> +		if (buffers[0].id != 10 ||
> +		    buffers[1].id != 11) {
> +			cerr << "mapBuffers(): Invalid buffer IDs" << endl;
> +			return report(Op_mapBuffers, TestFail);
> +		}
> +
> +		if (buffers[0].memory.planes().size() != 3 ||
> +		    buffers[1].memory.planes().size() != 3) {
> +			cerr << "mapBuffers(): Invalid number of planes" << endl;
> +			return report(Op_mapBuffers, TestFail);
> +		}
> +
> +		if (buffers[0].memory.planes()[0].length() != 4096 ||
> +		    buffers[0].memory.planes()[1].length() != 0 ||
> +		    buffers[0].memory.planes()[2].length() != 0 ||
> +		    buffers[0].memory.planes()[0].length() != 4096 ||
> +		    buffers[1].memory.planes()[1].length() != 4096 ||
> +		    buffers[1].memory.planes()[2].length() != 0) {
> +			cerr << "mapBuffers(): Invalid length" << endl;
> +			return report(Op_mapBuffers, TestFail);
> +		}
> +
> +		if (buffers[0].memory.planes()[0].dmabuf() == -1 ||
> +		    buffers[0].memory.planes()[1].dmabuf() != -1 ||
> +		    buffers[0].memory.planes()[2].dmabuf() != -1 ||
> +		    buffers[0].memory.planes()[0].dmabuf() == -1 ||
> +		    buffers[1].memory.planes()[1].dmabuf() == -1 ||
> +		    buffers[1].memory.planes()[2].dmabuf() != -1) {
> +			cerr << "mapBuffers(): Invalid dmabuf" << endl;
> +			return report(Op_mapBuffers, TestFail);
> +		}
> +
> +		report(Op_mapBuffers, TestPass);
> +	}
> +
> +	void unmapBuffers(const std::vector<unsigned int> &ids) override
> +	{
> +		if (ids.size() != 2) {
> +			cerr << "unmapBuffers(): Invalid number of ids "
> +			     << ids.size() << endl;
> +			return report(Op_unmapBuffers, TestFail);
> +		}
> +
> +		if (ids[0] != 10 || ids[1] != 11) {
> +			cerr << "unmapBuffers(): Invalid buffer IDs" << endl;
> +			return report(Op_unmapBuffers, TestFail);
> +		}
> +
> +		report(Op_unmapBuffers, TestPass);
> +	}
> +
> +	void processEvent(const IPAOperationData &data) override
> +	{
> +		/* Verify operation and data. */
> +		if (data.operation != Op_processEvent) {
> +			cerr << "processEvent(): Invalid operation "
> +			     << data.operation << endl;
> +			return report(Op_processEvent, TestFail);
> +		}
> +
> +		if (data.data != std::vector<unsigned int>{ 1, 2, 3, 4 }) {
> +			cerr << "processEvent(): Invalid data" << endl;
> +			return report(Op_processEvent, TestFail);
> +		}
> +
> +		/* Verify controls. */
> +		if (data.controls.size() != 1) {
> +			cerr << "processEvent(): Controls not found" << endl;
> +			return report(Op_processEvent, TestFail);
> +		}
> +
> +		const ControlList &controls = data.controls[0];
> +		if (controls.get(V4L2_CID_BRIGHTNESS).get<int32_t>() != 10 ||
> +		    controls.get(V4L2_CID_CONTRAST).get<int32_t>() != 20 ||
> +		    controls.get(V4L2_CID_SATURATION).get<int32_t>() != 30) {
> +			cerr << "processEvent(): Invalid controls" << endl;
> +			return report(Op_processEvent, TestFail);
> +		}
> +
> +		report(Op_processEvent, TestPass);
> +	}
> +
> +private:
> +	void report(Operation op, int status)
> +	{
> +		IPAOperationData data;
> +		data.operation = op;
> +		data.data.resize(1);
> +		data.data[0] = status;
> +		queueFrameAction.emit(sequence_++, data);
> +	}
> +
> +	unsigned int sequence_;
> +};
> +
> +#define INVOKE(method, ...) \
> +	invoke(&IPAInterface::method, Op_##method, #method, ##__VA_ARGS__)
> +
> +class IPAWrappersTest : public Test
> +{
> +public:
> +	IPAWrappersTest()
> +		: subdev_(nullptr), wrapper_(nullptr), sequence_(0), fd_(-1)
> +	{
> +	}
> +
> +protected:
> +	int init() override
> +	{
> +		/* Locate the VIMC Sensor B subdevice. */
> +		enumerator_ = unique_ptr<DeviceEnumerator>(DeviceEnumerator::create());
> +		if (!enumerator_) {
> +			cerr << "Failed to create device enumerator" << endl;
> +			return TestFail;
> +		}
> +
> +		if (enumerator_->enumerate()) {
> +			cerr << "Failed to enumerate media devices" << endl;
> +			return TestFail;
> +		}
> +
> +		DeviceMatch dm("vimc");
> +		media_ = enumerator_->search(dm);
> +		if (!media_) {
> +			cerr << "No VIMC media device found: skip test" << endl;
> +			return TestSkip;
> +		}
> +
> +		MediaEntity *entity = media_->getEntityByName("Sensor A");
> +		if (!entity) {
> +			cerr << "Unable to find media entity 'Sensor A'" << endl;
> +			return TestFail;
> +		}
> +
> +		subdev_ = new V4L2Subdevice(entity);
> +		if (subdev_->open() < 0) {
> +			cerr << "Unable to open 'Sensor A' subdevice" << endl;
> +			return TestFail;
> +		}
> +
> +		/* Force usage of the C API as that's what we want to test. */
> +		int ret = setenv("LIBCAMERA_IPA_FORCE_C_API", "", 1);
> +		if (ret)
> +			return TestFail;
> +
> +		std::unique_ptr<IPAInterface> intf = utils::make_unique<TestIPAInterface>();
> +		wrapper_ = new IPAContextWrapper(new IPAInterfaceWrapper(std::move(intf)));
> +		wrapper_->queueFrameAction.connect(this, &IPAWrappersTest::queueFrameAction);
> +
> +		/* Create a file descriptor for the buffer-related operations. */
> +		fd_ = open("/tmp", O_TMPFILE | O_RDWR, 0600);
> +		if (fd_ == -1)
> +			return TestFail;
> +
> +		ftruncate(fd_, 4096);
> +
> +		return TestPass;
> +	}
> +
> +	int run() override
> +	{
> +		int ret;
> +
> +		/* Test configure(). */
> +		std::map<unsigned int, IPAStream> config{
> +			{ 1, { V4L2_PIX_FMT_YUYV, { 1024, 768 } } },
> +			{ 2, { V4L2_PIX_FMT_NV12, { 800, 600 } } },
> +		};
> +		std::map<unsigned int, const ControlInfoMap &> controlInfo;
> +		controlInfo.emplace(42, subdev_->controls());
> +		ret = INVOKE(configure, config, controlInfo);
> +		if (ret == TestFail)
> +			return TestFail;
> +
> +		/* Test mapBuffers(). */
> +		std::vector<IPABuffer> buffers(2);
> +		buffers[0].memory.planes().resize(3);
> +		buffers[0].id = 10;
> +		buffers[0].memory.planes()[0].setDmabuf(fd_, 4096);
> +		buffers[1].id = 11;
> +		buffers[1].memory.planes().resize(3);
> +		buffers[1].memory.planes()[0].setDmabuf(fd_, 4096);
> +		buffers[1].memory.planes()[1].setDmabuf(fd_, 4096);
> +
> +		ret = INVOKE(mapBuffers, buffers);
> +		if (ret == TestFail)
> +			return TestFail;
> +
> +		/* Test unmapBuffers(). */
> +		std::vector<unsigned int> bufferIds = { 10, 11 };
> +		ret = INVOKE(unmapBuffers, bufferIds);
> +		if (ret == TestFail)
> +			return TestFail;
> +
> +		/* Test processEvent(). */
> +		IPAOperationData data;
> +		data.operation = Op_processEvent;
> +		data.data = { 1, 2, 3, 4 };
> +		data.controls.emplace_back(subdev_->controls());
> +
> +		ControlList &controls = data.controls.back();
> +		controls.set(V4L2_CID_BRIGHTNESS, static_cast<int32_t>(10));
> +		controls.set(V4L2_CID_CONTRAST, static_cast<int32_t>(20));
> +		controls.set(V4L2_CID_SATURATION, static_cast<int32_t>(30));
> +
> +		ret = INVOKE(processEvent, data);
> +		if (ret == TestFail)
> +			return TestFail;
> +
> +		/*
> +		 * Test init() last to ensure nothing in the wrappers or
> +		 * serializer depends on init() being called first.
> +		 */
> +		ret = INVOKE(init);
> +		if (ret == TestFail)
> +			return TestFail;
> +
> +		return TestPass;
> +	}
> +
> +	void cleanup() override
> +	{
> +		delete wrapper_;
> +		delete subdev_;
> +
> +		if (fd_ != -1)
> +			close(fd_);
> +	}
> +
> +private:
> +	template<typename T, typename... Args1, typename... Args2>
> +	int invoke(T (IPAInterface::*func)(Args1...), Operation op,
> +		   const char *name, Args2... args)
> +	{
> +		data_ = IPAOperationData();
> +		(wrapper_->*func)(args...);
> +
> +		if (frame_ != sequence_) {
> +			cerr << "IPAInterface::" << name
> +			     << "(): invalid frame number " << frame_
> +			     << ", expected " << sequence_;
> +			return TestFail;
> +		}
> +
> +		sequence_++;
> +
> +		if (data_.operation != op) {
> +			cerr << "IPAInterface::" << name
> +			     << "(): failed to propagate" << endl;
> +			return TestFail;
> +		}
> +
> +		if (data_.data[0] != TestPass) {
> +			cerr << "IPAInterface::" << name
> +			     << "(): reported an error" << endl;
> +			return TestFail;
> +		}
> +
> +		return TestPass;
> +	}
> +
> +	void queueFrameAction(unsigned int frame, const IPAOperationData &data)
> +	{
> +		frame_ = frame;
> +		data_ = data;
> +	}
> +
> +	std::shared_ptr<MediaDevice> media_;
> +	std::unique_ptr<DeviceEnumerator> enumerator_;
> +	V4L2Subdevice *subdev_;
> +
> +	IPAContextWrapper *wrapper_;
> +	IPAOperationData data_;
> +	unsigned int sequence_;
> +	unsigned int frame_;
> +	int fd_;
> +};
> +
> +TEST_REGISTER(IPAWrappersTest)
> diff --git a/test/ipa/meson.build b/test/ipa/meson.build
> index c501fcf99aed..f925c50a085e 100644
> --- a/test/ipa/meson.build
> +++ b/test/ipa/meson.build
> @@ -1,13 +1,14 @@
>  ipa_test = [
>      ['ipa_module_test',     'ipa_module_test.cpp'],
>      ['ipa_interface_test',  'ipa_interface_test.cpp'],
> +    ['ipa_wrappers_test',   'ipa_wrappers_test.cpp'],
>  ]
>  
>  foreach t : ipa_test
>      exe = executable(t[0], t[1],
>                       dependencies : libcamera_dep,
> -                     link_with : test_libraries,
> -                     include_directories : test_includes_internal)
> +                     link_with : [libipa, test_libraries],
> +                     include_directories : [libipa_includes, test_includes_internal])
>  
>      test(t[0], exe, suite : 'ipa')
>  endforeach
> -- 
> Regards,
> 
> Laurent Pinchart
> 
> _______________________________________________
> libcamera-devel mailing list
> libcamera-devel@lists.libcamera.org
> https://lists.libcamera.org/listinfo/libcamera-devel
Jacopo Mondi Nov. 19, 2019, 4:21 p.m. UTC | #2
Hi Laurent,

On Fri, Nov 08, 2019 at 10:54:08PM +0200, Laurent Pinchart wrote:
> Wrap an IPAInterface in an IPAInterfaceWrapper in an IPAContextWrapper,
> and verify that the translation between C and C++ APIs work correctly.
>
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>  test/ipa/ipa_wrappers_test.cpp | 390 +++++++++++++++++++++++++++++++++
>  test/ipa/meson.build           |   5 +-
>  2 files changed, 393 insertions(+), 2 deletions(-)
>  create mode 100644 test/ipa/ipa_wrappers_test.cpp
>
> diff --git a/test/ipa/ipa_wrappers_test.cpp b/test/ipa/ipa_wrappers_test.cpp
> new file mode 100644
> index 000000000000..c0717f0e301b
> --- /dev/null
> +++ b/test/ipa/ipa_wrappers_test.cpp
> @@ -0,0 +1,390 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * Copyright (C) 2019, Google Inc.
> + *
> + * ipa_wrappers_test.cpp - Test the IPA interface and context wrappers
> + */
> +
> +#include <fcntl.h>
> +#include <iostream>
> +#include <memory>
> +#include <linux/videodev2.h>
> +#include <sys/stat.h>
> +#include <unistd.h>
> +
> +#include <libcamera/controls.h>
> +#include <libipa/ipa_interface_wrapper.h>
> +
> +#include "device_enumerator.h"
> +#include "ipa_context_wrapper.h"
> +#include "media_device.h"
> +#include "utils.h"
> +#include "v4l2_subdevice.h"
> +
> +#include "test.h"
> +
> +using namespace libcamera;
> +using namespace std;
> +
> +enum Operation {
> +	Op_init,
> +	Op_configure,
> +	Op_mapBuffers,
> +	Op_unmapBuffers,
> +	Op_processEvent,
> +};
> +
> +class TestIPAInterface : public IPAInterface
> +{
> +public:
> +	TestIPAInterface()
> +		: sequence_(0)
> +	{
> +	}
> +
> +	int init() override
> +	{
> +		report(Op_init, TestPass);
> +		return 0;
> +	}
> +
> +	void configure(const std::map<unsigned int, IPAStream> &streamConfig,
> +		       const std::map<unsigned int, const ControlInfoMap &> &entityControls) override
> +	{
> +		/* Verify streamConfig. */
> +		if (streamConfig.size() != 2) {
> +			cerr << "configure(): Invalid number of streams "
> +			     << streamConfig.size() << endl;
> +			return report(Op_configure, TestFail);
> +		}
> +
> +		auto iter = streamConfig.find(1);
> +		if (iter == streamConfig.end()) {
> +			cerr << "configure(): No configuration for stream 1" << endl;
> +			return report(Op_configure, TestFail);
> +		}
> +		const IPAStream *stream = &iter->second;
> +		if (stream->pixelFormat != V4L2_PIX_FMT_YUYV ||
> +		    stream->size != Size{ 1024, 768 }) {
> +			cerr << "configure(): Invalid configuration for stream 1" << endl;
> +			return report(Op_configure, TestFail);
> +		}
> +
> +		iter = streamConfig.find(2);
> +		if (iter == streamConfig.end()) {
> +			cerr << "configure(): No configuration for stream 2" << endl;
> +			return report(Op_configure, TestFail);
> +		}
> +		stream = &iter->second;
> +		if (stream->pixelFormat != V4L2_PIX_FMT_NV12 ||
> +		    stream->size != Size{ 800, 600 }) {
> +			cerr << "configure(): Invalid configuration for stream 2" << endl;
> +			return report(Op_configure, TestFail);
> +		}
> +
> +		/* Verify entityControls. */
> +		auto ctrlIter = entityControls.find(42);
> +		if (ctrlIter == entityControls.end()) {
> +			cerr << "configure(): Controls not found" << endl;
> +			return report(Op_configure, TestFail);
> +		}
> +
> +		const ControlInfoMap &infoMap = ctrlIter->second;
> +
> +		if (infoMap.count(V4L2_CID_BRIGHTNESS) != 1 ||
> +		    infoMap.count(V4L2_CID_CONTRAST) != 1 ||
> +		    infoMap.count(V4L2_CID_SATURATION) != 1) {
> +			cerr << "configure(): Invalid control IDs" << endl;
> +			return report(Op_configure, TestFail);
> +		}
> +
> +		report(Op_configure, TestPass);
> +	}
> +
> +	void mapBuffers(const std::vector<IPABuffer> &buffers) override
> +	{
> +		if (buffers.size() != 2) {
> +			cerr << "mapBuffers(): Invalid number of buffers "
> +			     << buffers.size() << endl;
> +			return report(Op_mapBuffers, TestFail);
> +		}
> +
> +		if (buffers[0].id != 10 ||
> +		    buffers[1].id != 11) {
> +			cerr << "mapBuffers(): Invalid buffer IDs" << endl;
> +			return report(Op_mapBuffers, TestFail);
> +		}
> +
> +		if (buffers[0].memory.planes().size() != 3 ||
> +		    buffers[1].memory.planes().size() != 3) {
> +			cerr << "mapBuffers(): Invalid number of planes" << endl;
> +			return report(Op_mapBuffers, TestFail);
> +		}
> +
> +		if (buffers[0].memory.planes()[0].length() != 4096 ||
> +		    buffers[0].memory.planes()[1].length() != 0 ||
> +		    buffers[0].memory.planes()[2].length() != 0 ||
> +		    buffers[0].memory.planes()[0].length() != 4096 ||
> +		    buffers[1].memory.planes()[1].length() != 4096 ||
> +		    buffers[1].memory.planes()[2].length() != 0) {
> +			cerr << "mapBuffers(): Invalid length" << endl;
> +			return report(Op_mapBuffers, TestFail);
> +		}
> +
> +		if (buffers[0].memory.planes()[0].dmabuf() == -1 ||
> +		    buffers[0].memory.planes()[1].dmabuf() != -1 ||
> +		    buffers[0].memory.planes()[2].dmabuf() != -1 ||
> +		    buffers[0].memory.planes()[0].dmabuf() == -1 ||
> +		    buffers[1].memory.planes()[1].dmabuf() == -1 ||
> +		    buffers[1].memory.planes()[2].dmabuf() != -1) {
> +			cerr << "mapBuffers(): Invalid dmabuf" << endl;
> +			return report(Op_mapBuffers, TestFail);
> +		}
> +
> +		report(Op_mapBuffers, TestPass);
> +	}
> +
> +	void unmapBuffers(const std::vector<unsigned int> &ids) override
> +	{
> +		if (ids.size() != 2) {
> +			cerr << "unmapBuffers(): Invalid number of ids "
> +			     << ids.size() << endl;
> +			return report(Op_unmapBuffers, TestFail);
> +		}
> +
> +		if (ids[0] != 10 || ids[1] != 11) {
> +			cerr << "unmapBuffers(): Invalid buffer IDs" << endl;
> +			return report(Op_unmapBuffers, TestFail);
> +		}
> +
> +		report(Op_unmapBuffers, TestPass);
> +	}
> +
> +	void processEvent(const IPAOperationData &data) override
> +	{
> +		/* Verify operation and data. */
> +		if (data.operation != Op_processEvent) {
> +			cerr << "processEvent(): Invalid operation "
> +			     << data.operation << endl;
> +			return report(Op_processEvent, TestFail);
> +		}
> +
> +		if (data.data != std::vector<unsigned int>{ 1, 2, 3, 4 }) {
> +			cerr << "processEvent(): Invalid data" << endl;
> +			return report(Op_processEvent, TestFail);
> +		}
> +
> +		/* Verify controls. */
> +		if (data.controls.size() != 1) {
> +			cerr << "processEvent(): Controls not found" << endl;
> +			return report(Op_processEvent, TestFail);
> +		}
> +
> +		const ControlList &controls = data.controls[0];
> +		if (controls.get(V4L2_CID_BRIGHTNESS).get<int32_t>() != 10 ||
> +		    controls.get(V4L2_CID_CONTRAST).get<int32_t>() != 20 ||
> +		    controls.get(V4L2_CID_SATURATION).get<int32_t>() != 30) {
> +			cerr << "processEvent(): Invalid controls" << endl;
> +			return report(Op_processEvent, TestFail);
> +		}
> +
> +		report(Op_processEvent, TestPass);
> +	}
> +
> +private:
> +	void report(Operation op, int status)
> +	{
> +		IPAOperationData data;
> +		data.operation = op;
> +		data.data.resize(1);
> +		data.data[0] = status;
> +		queueFrameAction.emit(sequence_++, data);
> +	}
> +
> +	unsigned int sequence_;
> +};
> +
> +#define INVOKE(method, ...) \
> +	invoke(&IPAInterface::method, Op_##method, #method, ##__VA_ARGS__)
> +
> +class IPAWrappersTest : public Test
> +{
> +public:
> +	IPAWrappersTest()
> +		: subdev_(nullptr), wrapper_(nullptr), sequence_(0), fd_(-1)
> +	{
> +	}
> +
> +protected:
> +	int init() override
> +	{
> +		/* Locate the VIMC Sensor B subdevice. */
> +		enumerator_ = unique_ptr<DeviceEnumerator>(DeviceEnumerator::create());
> +		if (!enumerator_) {
> +			cerr << "Failed to create device enumerator" << endl;
> +			return TestFail;
> +		}
> +
> +		if (enumerator_->enumerate()) {
> +			cerr << "Failed to enumerate media devices" << endl;
> +			return TestFail;
> +		}
> +
> +		DeviceMatch dm("vimc");
> +		media_ = enumerator_->search(dm);
> +		if (!media_) {
> +			cerr << "No VIMC media device found: skip test" << endl;
> +			return TestSkip;
> +		}
> +
> +		MediaEntity *entity = media_->getEntityByName("Sensor A");
> +		if (!entity) {
> +			cerr << "Unable to find media entity 'Sensor A'" << endl;
> +			return TestFail;
> +		}
> +
> +		subdev_ = new V4L2Subdevice(entity);
> +		if (subdev_->open() < 0) {
> +			cerr << "Unable to open 'Sensor A' subdevice" << endl;
> +			return TestFail;
> +		}
> +
> +		/* Force usage of the C API as that's what we want to test. */
> +		int ret = setenv("LIBCAMERA_IPA_FORCE_C_API", "", 1);
> +		if (ret)
> +			return TestFail;
> +
> +		std::unique_ptr<IPAInterface> intf = utils::make_unique<TestIPAInterface>();
> +		wrapper_ = new IPAContextWrapper(new IPAInterfaceWrapper(std::move(intf)));
> +		wrapper_->queueFrameAction.connect(this, &IPAWrappersTest::queueFrameAction);
> +
> +		/* Create a file descriptor for the buffer-related operations. */
> +		fd_ = open("/tmp", O_TMPFILE | O_RDWR, 0600);
> +		if (fd_ == -1)
> +			return TestFail;
> +
> +		ftruncate(fd_, 4096);

When building on Chromium, which by default enables the -Wunused-result flag,
this line results in a build error.

libcamera/test/ipa/ipa_wrappers_test.cpp:266:3: error: ignoring return value of function declared with 'warn_unused_result' attribute [-Werror,-Wunused-result]


> +
> +		return TestPass;
> +	}
> +
> +	int run() override
> +	{
> +		int ret;
> +
> +		/* Test configure(). */
> +		std::map<unsigned int, IPAStream> config{
> +			{ 1, { V4L2_PIX_FMT_YUYV, { 1024, 768 } } },
> +			{ 2, { V4L2_PIX_FMT_NV12, { 800, 600 } } },
> +		};
> +		std::map<unsigned int, const ControlInfoMap &> controlInfo;
> +		controlInfo.emplace(42, subdev_->controls());
> +		ret = INVOKE(configure, config, controlInfo);
> +		if (ret == TestFail)
> +			return TestFail;
> +
> +		/* Test mapBuffers(). */
> +		std::vector<IPABuffer> buffers(2);
> +		buffers[0].memory.planes().resize(3);
> +		buffers[0].id = 10;
> +		buffers[0].memory.planes()[0].setDmabuf(fd_, 4096);
> +		buffers[1].id = 11;
> +		buffers[1].memory.planes().resize(3);
> +		buffers[1].memory.planes()[0].setDmabuf(fd_, 4096);
> +		buffers[1].memory.planes()[1].setDmabuf(fd_, 4096);
> +
> +		ret = INVOKE(mapBuffers, buffers);
> +		if (ret == TestFail)
> +			return TestFail;
> +
> +		/* Test unmapBuffers(). */
> +		std::vector<unsigned int> bufferIds = { 10, 11 };
> +		ret = INVOKE(unmapBuffers, bufferIds);
> +		if (ret == TestFail)
> +			return TestFail;
> +
> +		/* Test processEvent(). */
> +		IPAOperationData data;
> +		data.operation = Op_processEvent;
> +		data.data = { 1, 2, 3, 4 };
> +		data.controls.emplace_back(subdev_->controls());
> +
> +		ControlList &controls = data.controls.back();
> +		controls.set(V4L2_CID_BRIGHTNESS, static_cast<int32_t>(10));
> +		controls.set(V4L2_CID_CONTRAST, static_cast<int32_t>(20));
> +		controls.set(V4L2_CID_SATURATION, static_cast<int32_t>(30));
> +
> +		ret = INVOKE(processEvent, data);
> +		if (ret == TestFail)
> +			return TestFail;
> +
> +		/*
> +		 * Test init() last to ensure nothing in the wrappers or
> +		 * serializer depends on init() being called first.
> +		 */
> +		ret = INVOKE(init);
> +		if (ret == TestFail)
> +			return TestFail;
> +
> +		return TestPass;
> +	}
> +
> +	void cleanup() override
> +	{
> +		delete wrapper_;
> +		delete subdev_;
> +
> +		if (fd_ != -1)
> +			close(fd_);
> +	}
> +
> +private:
> +	template<typename T, typename... Args1, typename... Args2>
> +	int invoke(T (IPAInterface::*func)(Args1...), Operation op,
> +		   const char *name, Args2... args)
> +	{
> +		data_ = IPAOperationData();
> +		(wrapper_->*func)(args...);
> +
> +		if (frame_ != sequence_) {
> +			cerr << "IPAInterface::" << name
> +			     << "(): invalid frame number " << frame_
> +			     << ", expected " << sequence_;
> +			return TestFail;
> +		}
> +
> +		sequence_++;
> +
> +		if (data_.operation != op) {
> +			cerr << "IPAInterface::" << name
> +			     << "(): failed to propagate" << endl;
> +			return TestFail;
> +		}
> +
> +		if (data_.data[0] != TestPass) {
> +			cerr << "IPAInterface::" << name
> +			     << "(): reported an error" << endl;
> +			return TestFail;
> +		}
> +
> +		return TestPass;
> +	}
> +
> +	void queueFrameAction(unsigned int frame, const IPAOperationData &data)
> +	{
> +		frame_ = frame;
> +		data_ = data;
> +	}
> +
> +	std::shared_ptr<MediaDevice> media_;
> +	std::unique_ptr<DeviceEnumerator> enumerator_;
> +	V4L2Subdevice *subdev_;
> +
> +	IPAContextWrapper *wrapper_;
> +	IPAOperationData data_;
> +	unsigned int sequence_;
> +	unsigned int frame_;
> +	int fd_;
> +};
> +
> +TEST_REGISTER(IPAWrappersTest)
> diff --git a/test/ipa/meson.build b/test/ipa/meson.build
> index c501fcf99aed..f925c50a085e 100644
> --- a/test/ipa/meson.build
> +++ b/test/ipa/meson.build
> @@ -1,13 +1,14 @@
>  ipa_test = [
>      ['ipa_module_test',     'ipa_module_test.cpp'],
>      ['ipa_interface_test',  'ipa_interface_test.cpp'],
> +    ['ipa_wrappers_test',   'ipa_wrappers_test.cpp'],
>  ]
>
>  foreach t : ipa_test
>      exe = executable(t[0], t[1],
>                       dependencies : libcamera_dep,
> -                     link_with : test_libraries,
> -                     include_directories : test_includes_internal)
> +                     link_with : [libipa, test_libraries],
> +                     include_directories : [libipa_includes, test_includes_internal])
>
>      test(t[0], exe, suite : 'ipa')
>  endforeach
> --
> Regards,
>
> Laurent Pinchart
>
> _______________________________________________
> libcamera-devel mailing list
> libcamera-devel@lists.libcamera.org
> https://lists.libcamera.org/listinfo/libcamera-devel
Laurent Pinchart Nov. 19, 2019, 10:46 p.m. UTC | #3
Hi Kieran,

On Tue, Nov 19, 2019 at 05:21:53PM +0100, Jacopo Mondi wrote:
> On Fri, Nov 08, 2019 at 10:54:08PM +0200, Laurent Pinchart wrote:
> > Wrap an IPAInterface in an IPAInterfaceWrapper in an IPAContextWrapper,
> > and verify that the translation between C and C++ APIs work correctly.
> >
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > ---
> >  test/ipa/ipa_wrappers_test.cpp | 390 +++++++++++++++++++++++++++++++++
> >  test/ipa/meson.build           |   5 +-
> >  2 files changed, 393 insertions(+), 2 deletions(-)
> >  create mode 100644 test/ipa/ipa_wrappers_test.cpp
> >
> > diff --git a/test/ipa/ipa_wrappers_test.cpp b/test/ipa/ipa_wrappers_test.cpp
> > new file mode 100644
> > index 000000000000..c0717f0e301b
> > --- /dev/null
> > +++ b/test/ipa/ipa_wrappers_test.cpp
> > @@ -0,0 +1,390 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> > +/*
> > + * Copyright (C) 2019, Google Inc.
> > + *
> > + * ipa_wrappers_test.cpp - Test the IPA interface and context wrappers
> > + */
> > +
> > +#include <fcntl.h>
> > +#include <iostream>
> > +#include <memory>
> > +#include <linux/videodev2.h>
> > +#include <sys/stat.h>
> > +#include <unistd.h>
> > +
> > +#include <libcamera/controls.h>
> > +#include <libipa/ipa_interface_wrapper.h>
> > +
> > +#include "device_enumerator.h"
> > +#include "ipa_context_wrapper.h"
> > +#include "media_device.h"
> > +#include "utils.h"
> > +#include "v4l2_subdevice.h"
> > +
> > +#include "test.h"
> > +
> > +using namespace libcamera;
> > +using namespace std;
> > +
> > +enum Operation {
> > +	Op_init,
> > +	Op_configure,
> > +	Op_mapBuffers,
> > +	Op_unmapBuffers,
> > +	Op_processEvent,
> > +};
> > +
> > +class TestIPAInterface : public IPAInterface
> > +{
> > +public:
> > +	TestIPAInterface()
> > +		: sequence_(0)
> > +	{
> > +	}
> > +
> > +	int init() override
> > +	{
> > +		report(Op_init, TestPass);
> > +		return 0;
> > +	}
> > +
> > +	void configure(const std::map<unsigned int, IPAStream> &streamConfig,
> > +		       const std::map<unsigned int, const ControlInfoMap &> &entityControls) override
> > +	{
> > +		/* Verify streamConfig. */
> > +		if (streamConfig.size() != 2) {
> > +			cerr << "configure(): Invalid number of streams "
> > +			     << streamConfig.size() << endl;
> > +			return report(Op_configure, TestFail);
> > +		}
> > +
> > +		auto iter = streamConfig.find(1);
> > +		if (iter == streamConfig.end()) {
> > +			cerr << "configure(): No configuration for stream 1" << endl;
> > +			return report(Op_configure, TestFail);
> > +		}
> > +		const IPAStream *stream = &iter->second;
> > +		if (stream->pixelFormat != V4L2_PIX_FMT_YUYV ||
> > +		    stream->size != Size{ 1024, 768 }) {
> > +			cerr << "configure(): Invalid configuration for stream 1" << endl;
> > +			return report(Op_configure, TestFail);
> > +		}
> > +
> > +		iter = streamConfig.find(2);
> > +		if (iter == streamConfig.end()) {
> > +			cerr << "configure(): No configuration for stream 2" << endl;
> > +			return report(Op_configure, TestFail);
> > +		}
> > +		stream = &iter->second;
> > +		if (stream->pixelFormat != V4L2_PIX_FMT_NV12 ||
> > +		    stream->size != Size{ 800, 600 }) {
> > +			cerr << "configure(): Invalid configuration for stream 2" << endl;
> > +			return report(Op_configure, TestFail);
> > +		}
> > +
> > +		/* Verify entityControls. */
> > +		auto ctrlIter = entityControls.find(42);
> > +		if (ctrlIter == entityControls.end()) {
> > +			cerr << "configure(): Controls not found" << endl;
> > +			return report(Op_configure, TestFail);
> > +		}
> > +
> > +		const ControlInfoMap &infoMap = ctrlIter->second;
> > +
> > +		if (infoMap.count(V4L2_CID_BRIGHTNESS) != 1 ||
> > +		    infoMap.count(V4L2_CID_CONTRAST) != 1 ||
> > +		    infoMap.count(V4L2_CID_SATURATION) != 1) {
> > +			cerr << "configure(): Invalid control IDs" << endl;
> > +			return report(Op_configure, TestFail);
> > +		}
> > +
> > +		report(Op_configure, TestPass);
> > +	}
> > +
> > +	void mapBuffers(const std::vector<IPABuffer> &buffers) override
> > +	{
> > +		if (buffers.size() != 2) {
> > +			cerr << "mapBuffers(): Invalid number of buffers "
> > +			     << buffers.size() << endl;
> > +			return report(Op_mapBuffers, TestFail);
> > +		}
> > +
> > +		if (buffers[0].id != 10 ||
> > +		    buffers[1].id != 11) {
> > +			cerr << "mapBuffers(): Invalid buffer IDs" << endl;
> > +			return report(Op_mapBuffers, TestFail);
> > +		}
> > +
> > +		if (buffers[0].memory.planes().size() != 3 ||
> > +		    buffers[1].memory.planes().size() != 3) {
> > +			cerr << "mapBuffers(): Invalid number of planes" << endl;
> > +			return report(Op_mapBuffers, TestFail);
> > +		}
> > +
> > +		if (buffers[0].memory.planes()[0].length() != 4096 ||
> > +		    buffers[0].memory.planes()[1].length() != 0 ||
> > +		    buffers[0].memory.planes()[2].length() != 0 ||
> > +		    buffers[0].memory.planes()[0].length() != 4096 ||
> > +		    buffers[1].memory.planes()[1].length() != 4096 ||
> > +		    buffers[1].memory.planes()[2].length() != 0) {
> > +			cerr << "mapBuffers(): Invalid length" << endl;
> > +			return report(Op_mapBuffers, TestFail);
> > +		}
> > +
> > +		if (buffers[0].memory.planes()[0].dmabuf() == -1 ||
> > +		    buffers[0].memory.planes()[1].dmabuf() != -1 ||
> > +		    buffers[0].memory.planes()[2].dmabuf() != -1 ||
> > +		    buffers[0].memory.planes()[0].dmabuf() == -1 ||
> > +		    buffers[1].memory.planes()[1].dmabuf() == -1 ||
> > +		    buffers[1].memory.planes()[2].dmabuf() != -1) {
> > +			cerr << "mapBuffers(): Invalid dmabuf" << endl;
> > +			return report(Op_mapBuffers, TestFail);
> > +		}
> > +
> > +		report(Op_mapBuffers, TestPass);
> > +	}
> > +
> > +	void unmapBuffers(const std::vector<unsigned int> &ids) override
> > +	{
> > +		if (ids.size() != 2) {
> > +			cerr << "unmapBuffers(): Invalid number of ids "
> > +			     << ids.size() << endl;
> > +			return report(Op_unmapBuffers, TestFail);
> > +		}
> > +
> > +		if (ids[0] != 10 || ids[1] != 11) {
> > +			cerr << "unmapBuffers(): Invalid buffer IDs" << endl;
> > +			return report(Op_unmapBuffers, TestFail);
> > +		}
> > +
> > +		report(Op_unmapBuffers, TestPass);
> > +	}
> > +
> > +	void processEvent(const IPAOperationData &data) override
> > +	{
> > +		/* Verify operation and data. */
> > +		if (data.operation != Op_processEvent) {
> > +			cerr << "processEvent(): Invalid operation "
> > +			     << data.operation << endl;
> > +			return report(Op_processEvent, TestFail);
> > +		}
> > +
> > +		if (data.data != std::vector<unsigned int>{ 1, 2, 3, 4 }) {
> > +			cerr << "processEvent(): Invalid data" << endl;
> > +			return report(Op_processEvent, TestFail);
> > +		}
> > +
> > +		/* Verify controls. */
> > +		if (data.controls.size() != 1) {
> > +			cerr << "processEvent(): Controls not found" << endl;
> > +			return report(Op_processEvent, TestFail);
> > +		}
> > +
> > +		const ControlList &controls = data.controls[0];
> > +		if (controls.get(V4L2_CID_BRIGHTNESS).get<int32_t>() != 10 ||
> > +		    controls.get(V4L2_CID_CONTRAST).get<int32_t>() != 20 ||
> > +		    controls.get(V4L2_CID_SATURATION).get<int32_t>() != 30) {
> > +			cerr << "processEvent(): Invalid controls" << endl;
> > +			return report(Op_processEvent, TestFail);
> > +		}
> > +
> > +		report(Op_processEvent, TestPass);
> > +	}
> > +
> > +private:
> > +	void report(Operation op, int status)
> > +	{
> > +		IPAOperationData data;
> > +		data.operation = op;
> > +		data.data.resize(1);
> > +		data.data[0] = status;
> > +		queueFrameAction.emit(sequence_++, data);
> > +	}
> > +
> > +	unsigned int sequence_;
> > +};
> > +
> > +#define INVOKE(method, ...) \
> > +	invoke(&IPAInterface::method, Op_##method, #method, ##__VA_ARGS__)
> > +
> > +class IPAWrappersTest : public Test
> > +{
> > +public:
> > +	IPAWrappersTest()
> > +		: subdev_(nullptr), wrapper_(nullptr), sequence_(0), fd_(-1)
> > +	{
> > +	}
> > +
> > +protected:
> > +	int init() override
> > +	{
> > +		/* Locate the VIMC Sensor B subdevice. */
> > +		enumerator_ = unique_ptr<DeviceEnumerator>(DeviceEnumerator::create());
> > +		if (!enumerator_) {
> > +			cerr << "Failed to create device enumerator" << endl;
> > +			return TestFail;
> > +		}
> > +
> > +		if (enumerator_->enumerate()) {
> > +			cerr << "Failed to enumerate media devices" << endl;
> > +			return TestFail;
> > +		}
> > +
> > +		DeviceMatch dm("vimc");
> > +		media_ = enumerator_->search(dm);
> > +		if (!media_) {
> > +			cerr << "No VIMC media device found: skip test" << endl;
> > +			return TestSkip;
> > +		}
> > +
> > +		MediaEntity *entity = media_->getEntityByName("Sensor A");
> > +		if (!entity) {
> > +			cerr << "Unable to find media entity 'Sensor A'" << endl;
> > +			return TestFail;
> > +		}
> > +
> > +		subdev_ = new V4L2Subdevice(entity);
> > +		if (subdev_->open() < 0) {
> > +			cerr << "Unable to open 'Sensor A' subdevice" << endl;
> > +			return TestFail;
> > +		}
> > +
> > +		/* Force usage of the C API as that's what we want to test. */
> > +		int ret = setenv("LIBCAMERA_IPA_FORCE_C_API", "", 1);
> > +		if (ret)
> > +			return TestFail;
> > +
> > +		std::unique_ptr<IPAInterface> intf = utils::make_unique<TestIPAInterface>();
> > +		wrapper_ = new IPAContextWrapper(new IPAInterfaceWrapper(std::move(intf)));
> > +		wrapper_->queueFrameAction.connect(this, &IPAWrappersTest::queueFrameAction);
> > +
> > +		/* Create a file descriptor for the buffer-related operations. */
> > +		fd_ = open("/tmp", O_TMPFILE | O_RDWR, 0600);
> > +		if (fd_ == -1)
> > +			return TestFail;
> > +
> > +		ftruncate(fd_, 4096);
> 
> When building on Chromium, which by default enables the -Wunused-result flag,
> this line results in a build error.
> 
> libcamera/test/ipa/ipa_wrappers_test.cpp:266:3: error: ignoring return value of function declared with 'warn_unused_result' attribute [-Werror,-Wunused-result]

Could it be a libc version issue ? Adding -Wunused-result didn't produce
any warning for me.

I'll fix it nonetheless.

> > +
> > +		return TestPass;
> > +	}
> > +
> > +	int run() override
> > +	{
> > +		int ret;
> > +
> > +		/* Test configure(). */
> > +		std::map<unsigned int, IPAStream> config{
> > +			{ 1, { V4L2_PIX_FMT_YUYV, { 1024, 768 } } },
> > +			{ 2, { V4L2_PIX_FMT_NV12, { 800, 600 } } },
> > +		};
> > +		std::map<unsigned int, const ControlInfoMap &> controlInfo;
> > +		controlInfo.emplace(42, subdev_->controls());
> > +		ret = INVOKE(configure, config, controlInfo);
> > +		if (ret == TestFail)
> > +			return TestFail;
> > +
> > +		/* Test mapBuffers(). */
> > +		std::vector<IPABuffer> buffers(2);
> > +		buffers[0].memory.planes().resize(3);
> > +		buffers[0].id = 10;
> > +		buffers[0].memory.planes()[0].setDmabuf(fd_, 4096);
> > +		buffers[1].id = 11;
> > +		buffers[1].memory.planes().resize(3);
> > +		buffers[1].memory.planes()[0].setDmabuf(fd_, 4096);
> > +		buffers[1].memory.planes()[1].setDmabuf(fd_, 4096);
> > +
> > +		ret = INVOKE(mapBuffers, buffers);
> > +		if (ret == TestFail)
> > +			return TestFail;
> > +
> > +		/* Test unmapBuffers(). */
> > +		std::vector<unsigned int> bufferIds = { 10, 11 };
> > +		ret = INVOKE(unmapBuffers, bufferIds);
> > +		if (ret == TestFail)
> > +			return TestFail;
> > +
> > +		/* Test processEvent(). */
> > +		IPAOperationData data;
> > +		data.operation = Op_processEvent;
> > +		data.data = { 1, 2, 3, 4 };
> > +		data.controls.emplace_back(subdev_->controls());
> > +
> > +		ControlList &controls = data.controls.back();
> > +		controls.set(V4L2_CID_BRIGHTNESS, static_cast<int32_t>(10));
> > +		controls.set(V4L2_CID_CONTRAST, static_cast<int32_t>(20));
> > +		controls.set(V4L2_CID_SATURATION, static_cast<int32_t>(30));
> > +
> > +		ret = INVOKE(processEvent, data);
> > +		if (ret == TestFail)
> > +			return TestFail;
> > +
> > +		/*
> > +		 * Test init() last to ensure nothing in the wrappers or
> > +		 * serializer depends on init() being called first.
> > +		 */
> > +		ret = INVOKE(init);
> > +		if (ret == TestFail)
> > +			return TestFail;
> > +
> > +		return TestPass;
> > +	}
> > +
> > +	void cleanup() override
> > +	{
> > +		delete wrapper_;
> > +		delete subdev_;
> > +
> > +		if (fd_ != -1)
> > +			close(fd_);
> > +	}
> > +
> > +private:
> > +	template<typename T, typename... Args1, typename... Args2>
> > +	int invoke(T (IPAInterface::*func)(Args1...), Operation op,
> > +		   const char *name, Args2... args)
> > +	{
> > +		data_ = IPAOperationData();
> > +		(wrapper_->*func)(args...);
> > +
> > +		if (frame_ != sequence_) {
> > +			cerr << "IPAInterface::" << name
> > +			     << "(): invalid frame number " << frame_
> > +			     << ", expected " << sequence_;
> > +			return TestFail;
> > +		}
> > +
> > +		sequence_++;
> > +
> > +		if (data_.operation != op) {
> > +			cerr << "IPAInterface::" << name
> > +			     << "(): failed to propagate" << endl;
> > +			return TestFail;
> > +		}
> > +
> > +		if (data_.data[0] != TestPass) {
> > +			cerr << "IPAInterface::" << name
> > +			     << "(): reported an error" << endl;
> > +			return TestFail;
> > +		}
> > +
> > +		return TestPass;
> > +	}
> > +
> > +	void queueFrameAction(unsigned int frame, const IPAOperationData &data)
> > +	{
> > +		frame_ = frame;
> > +		data_ = data;
> > +	}
> > +
> > +	std::shared_ptr<MediaDevice> media_;
> > +	std::unique_ptr<DeviceEnumerator> enumerator_;
> > +	V4L2Subdevice *subdev_;
> > +
> > +	IPAContextWrapper *wrapper_;
> > +	IPAOperationData data_;
> > +	unsigned int sequence_;
> > +	unsigned int frame_;
> > +	int fd_;
> > +};
> > +
> > +TEST_REGISTER(IPAWrappersTest)
> > diff --git a/test/ipa/meson.build b/test/ipa/meson.build
> > index c501fcf99aed..f925c50a085e 100644
> > --- a/test/ipa/meson.build
> > +++ b/test/ipa/meson.build
> > @@ -1,13 +1,14 @@
> >  ipa_test = [
> >      ['ipa_module_test',     'ipa_module_test.cpp'],
> >      ['ipa_interface_test',  'ipa_interface_test.cpp'],
> > +    ['ipa_wrappers_test',   'ipa_wrappers_test.cpp'],
> >  ]
> >
> >  foreach t : ipa_test
> >      exe = executable(t[0], t[1],
> >                       dependencies : libcamera_dep,
> > -                     link_with : test_libraries,
> > -                     include_directories : test_includes_internal)
> > +                     link_with : [libipa, test_libraries],
> > +                     include_directories : [libipa_includes, test_includes_internal])
> >
> >      test(t[0], exe, suite : 'ipa')
> >  endforeach

Patch

diff --git a/test/ipa/ipa_wrappers_test.cpp b/test/ipa/ipa_wrappers_test.cpp
new file mode 100644
index 000000000000..c0717f0e301b
--- /dev/null
+++ b/test/ipa/ipa_wrappers_test.cpp
@@ -0,0 +1,390 @@ 
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * ipa_wrappers_test.cpp - Test the IPA interface and context wrappers
+ */
+
+#include <fcntl.h>
+#include <iostream>
+#include <memory>
+#include <linux/videodev2.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <libcamera/controls.h>
+#include <libipa/ipa_interface_wrapper.h>
+
+#include "device_enumerator.h"
+#include "ipa_context_wrapper.h"
+#include "media_device.h"
+#include "utils.h"
+#include "v4l2_subdevice.h"
+
+#include "test.h"
+
+using namespace libcamera;
+using namespace std;
+
+enum Operation {
+	Op_init,
+	Op_configure,
+	Op_mapBuffers,
+	Op_unmapBuffers,
+	Op_processEvent,
+};
+
+class TestIPAInterface : public IPAInterface
+{
+public:
+	TestIPAInterface()
+		: sequence_(0)
+	{
+	}
+
+	int init() override
+	{
+		report(Op_init, TestPass);
+		return 0;
+	}
+
+	void configure(const std::map<unsigned int, IPAStream> &streamConfig,
+		       const std::map<unsigned int, const ControlInfoMap &> &entityControls) override
+	{
+		/* Verify streamConfig. */
+		if (streamConfig.size() != 2) {
+			cerr << "configure(): Invalid number of streams "
+			     << streamConfig.size() << endl;
+			return report(Op_configure, TestFail);
+		}
+
+		auto iter = streamConfig.find(1);
+		if (iter == streamConfig.end()) {
+			cerr << "configure(): No configuration for stream 1" << endl;
+			return report(Op_configure, TestFail);
+		}
+		const IPAStream *stream = &iter->second;
+		if (stream->pixelFormat != V4L2_PIX_FMT_YUYV ||
+		    stream->size != Size{ 1024, 768 }) {
+			cerr << "configure(): Invalid configuration for stream 1" << endl;
+			return report(Op_configure, TestFail);
+		}
+
+		iter = streamConfig.find(2);
+		if (iter == streamConfig.end()) {
+			cerr << "configure(): No configuration for stream 2" << endl;
+			return report(Op_configure, TestFail);
+		}
+		stream = &iter->second;
+		if (stream->pixelFormat != V4L2_PIX_FMT_NV12 ||
+		    stream->size != Size{ 800, 600 }) {
+			cerr << "configure(): Invalid configuration for stream 2" << endl;
+			return report(Op_configure, TestFail);
+		}
+
+		/* Verify entityControls. */
+		auto ctrlIter = entityControls.find(42);
+		if (ctrlIter == entityControls.end()) {
+			cerr << "configure(): Controls not found" << endl;
+			return report(Op_configure, TestFail);
+		}
+
+		const ControlInfoMap &infoMap = ctrlIter->second;
+
+		if (infoMap.count(V4L2_CID_BRIGHTNESS) != 1 ||
+		    infoMap.count(V4L2_CID_CONTRAST) != 1 ||
+		    infoMap.count(V4L2_CID_SATURATION) != 1) {
+			cerr << "configure(): Invalid control IDs" << endl;
+			return report(Op_configure, TestFail);
+		}
+
+		report(Op_configure, TestPass);
+	}
+
+	void mapBuffers(const std::vector<IPABuffer> &buffers) override
+	{
+		if (buffers.size() != 2) {
+			cerr << "mapBuffers(): Invalid number of buffers "
+			     << buffers.size() << endl;
+			return report(Op_mapBuffers, TestFail);
+		}
+
+		if (buffers[0].id != 10 ||
+		    buffers[1].id != 11) {
+			cerr << "mapBuffers(): Invalid buffer IDs" << endl;
+			return report(Op_mapBuffers, TestFail);
+		}
+
+		if (buffers[0].memory.planes().size() != 3 ||
+		    buffers[1].memory.planes().size() != 3) {
+			cerr << "mapBuffers(): Invalid number of planes" << endl;
+			return report(Op_mapBuffers, TestFail);
+		}
+
+		if (buffers[0].memory.planes()[0].length() != 4096 ||
+		    buffers[0].memory.planes()[1].length() != 0 ||
+		    buffers[0].memory.planes()[2].length() != 0 ||
+		    buffers[0].memory.planes()[0].length() != 4096 ||
+		    buffers[1].memory.planes()[1].length() != 4096 ||
+		    buffers[1].memory.planes()[2].length() != 0) {
+			cerr << "mapBuffers(): Invalid length" << endl;
+			return report(Op_mapBuffers, TestFail);
+		}
+
+		if (buffers[0].memory.planes()[0].dmabuf() == -1 ||
+		    buffers[0].memory.planes()[1].dmabuf() != -1 ||
+		    buffers[0].memory.planes()[2].dmabuf() != -1 ||
+		    buffers[0].memory.planes()[0].dmabuf() == -1 ||
+		    buffers[1].memory.planes()[1].dmabuf() == -1 ||
+		    buffers[1].memory.planes()[2].dmabuf() != -1) {
+			cerr << "mapBuffers(): Invalid dmabuf" << endl;
+			return report(Op_mapBuffers, TestFail);
+		}
+
+		report(Op_mapBuffers, TestPass);
+	}
+
+	void unmapBuffers(const std::vector<unsigned int> &ids) override
+	{
+		if (ids.size() != 2) {
+			cerr << "unmapBuffers(): Invalid number of ids "
+			     << ids.size() << endl;
+			return report(Op_unmapBuffers, TestFail);
+		}
+
+		if (ids[0] != 10 || ids[1] != 11) {
+			cerr << "unmapBuffers(): Invalid buffer IDs" << endl;
+			return report(Op_unmapBuffers, TestFail);
+		}
+
+		report(Op_unmapBuffers, TestPass);
+	}
+
+	void processEvent(const IPAOperationData &data) override
+	{
+		/* Verify operation and data. */
+		if (data.operation != Op_processEvent) {
+			cerr << "processEvent(): Invalid operation "
+			     << data.operation << endl;
+			return report(Op_processEvent, TestFail);
+		}
+
+		if (data.data != std::vector<unsigned int>{ 1, 2, 3, 4 }) {
+			cerr << "processEvent(): Invalid data" << endl;
+			return report(Op_processEvent, TestFail);
+		}
+
+		/* Verify controls. */
+		if (data.controls.size() != 1) {
+			cerr << "processEvent(): Controls not found" << endl;
+			return report(Op_processEvent, TestFail);
+		}
+
+		const ControlList &controls = data.controls[0];
+		if (controls.get(V4L2_CID_BRIGHTNESS).get<int32_t>() != 10 ||
+		    controls.get(V4L2_CID_CONTRAST).get<int32_t>() != 20 ||
+		    controls.get(V4L2_CID_SATURATION).get<int32_t>() != 30) {
+			cerr << "processEvent(): Invalid controls" << endl;
+			return report(Op_processEvent, TestFail);
+		}
+
+		report(Op_processEvent, TestPass);
+	}
+
+private:
+	void report(Operation op, int status)
+	{
+		IPAOperationData data;
+		data.operation = op;
+		data.data.resize(1);
+		data.data[0] = status;
+		queueFrameAction.emit(sequence_++, data);
+	}
+
+	unsigned int sequence_;
+};
+
+#define INVOKE(method, ...) \
+	invoke(&IPAInterface::method, Op_##method, #method, ##__VA_ARGS__)
+
+class IPAWrappersTest : public Test
+{
+public:
+	IPAWrappersTest()
+		: subdev_(nullptr), wrapper_(nullptr), sequence_(0), fd_(-1)
+	{
+	}
+
+protected:
+	int init() override
+	{
+		/* Locate the VIMC Sensor B subdevice. */
+		enumerator_ = unique_ptr<DeviceEnumerator>(DeviceEnumerator::create());
+		if (!enumerator_) {
+			cerr << "Failed to create device enumerator" << endl;
+			return TestFail;
+		}
+
+		if (enumerator_->enumerate()) {
+			cerr << "Failed to enumerate media devices" << endl;
+			return TestFail;
+		}
+
+		DeviceMatch dm("vimc");
+		media_ = enumerator_->search(dm);
+		if (!media_) {
+			cerr << "No VIMC media device found: skip test" << endl;
+			return TestSkip;
+		}
+
+		MediaEntity *entity = media_->getEntityByName("Sensor A");
+		if (!entity) {
+			cerr << "Unable to find media entity 'Sensor A'" << endl;
+			return TestFail;
+		}
+
+		subdev_ = new V4L2Subdevice(entity);
+		if (subdev_->open() < 0) {
+			cerr << "Unable to open 'Sensor A' subdevice" << endl;
+			return TestFail;
+		}
+
+		/* Force usage of the C API as that's what we want to test. */
+		int ret = setenv("LIBCAMERA_IPA_FORCE_C_API", "", 1);
+		if (ret)
+			return TestFail;
+
+		std::unique_ptr<IPAInterface> intf = utils::make_unique<TestIPAInterface>();
+		wrapper_ = new IPAContextWrapper(new IPAInterfaceWrapper(std::move(intf)));
+		wrapper_->queueFrameAction.connect(this, &IPAWrappersTest::queueFrameAction);
+
+		/* Create a file descriptor for the buffer-related operations. */
+		fd_ = open("/tmp", O_TMPFILE | O_RDWR, 0600);
+		if (fd_ == -1)
+			return TestFail;
+
+		ftruncate(fd_, 4096);
+
+		return TestPass;
+	}
+
+	int run() override
+	{
+		int ret;
+
+		/* Test configure(). */
+		std::map<unsigned int, IPAStream> config{
+			{ 1, { V4L2_PIX_FMT_YUYV, { 1024, 768 } } },
+			{ 2, { V4L2_PIX_FMT_NV12, { 800, 600 } } },
+		};
+		std::map<unsigned int, const ControlInfoMap &> controlInfo;
+		controlInfo.emplace(42, subdev_->controls());
+		ret = INVOKE(configure, config, controlInfo);
+		if (ret == TestFail)
+			return TestFail;
+
+		/* Test mapBuffers(). */
+		std::vector<IPABuffer> buffers(2);
+		buffers[0].memory.planes().resize(3);
+		buffers[0].id = 10;
+		buffers[0].memory.planes()[0].setDmabuf(fd_, 4096);
+		buffers[1].id = 11;
+		buffers[1].memory.planes().resize(3);
+		buffers[1].memory.planes()[0].setDmabuf(fd_, 4096);
+		buffers[1].memory.planes()[1].setDmabuf(fd_, 4096);
+
+		ret = INVOKE(mapBuffers, buffers);
+		if (ret == TestFail)
+			return TestFail;
+
+		/* Test unmapBuffers(). */
+		std::vector<unsigned int> bufferIds = { 10, 11 };
+		ret = INVOKE(unmapBuffers, bufferIds);
+		if (ret == TestFail)
+			return TestFail;
+
+		/* Test processEvent(). */
+		IPAOperationData data;
+		data.operation = Op_processEvent;
+		data.data = { 1, 2, 3, 4 };
+		data.controls.emplace_back(subdev_->controls());
+
+		ControlList &controls = data.controls.back();
+		controls.set(V4L2_CID_BRIGHTNESS, static_cast<int32_t>(10));
+		controls.set(V4L2_CID_CONTRAST, static_cast<int32_t>(20));
+		controls.set(V4L2_CID_SATURATION, static_cast<int32_t>(30));
+
+		ret = INVOKE(processEvent, data);
+		if (ret == TestFail)
+			return TestFail;
+
+		/*
+		 * Test init() last to ensure nothing in the wrappers or
+		 * serializer depends on init() being called first.
+		 */
+		ret = INVOKE(init);
+		if (ret == TestFail)
+			return TestFail;
+
+		return TestPass;
+	}
+
+	void cleanup() override
+	{
+		delete wrapper_;
+		delete subdev_;
+
+		if (fd_ != -1)
+			close(fd_);
+	}
+
+private:
+	template<typename T, typename... Args1, typename... Args2>
+	int invoke(T (IPAInterface::*func)(Args1...), Operation op,
+		   const char *name, Args2... args)
+	{
+		data_ = IPAOperationData();
+		(wrapper_->*func)(args...);
+
+		if (frame_ != sequence_) {
+			cerr << "IPAInterface::" << name
+			     << "(): invalid frame number " << frame_
+			     << ", expected " << sequence_;
+			return TestFail;
+		}
+
+		sequence_++;
+
+		if (data_.operation != op) {
+			cerr << "IPAInterface::" << name
+			     << "(): failed to propagate" << endl;
+			return TestFail;
+		}
+
+		if (data_.data[0] != TestPass) {
+			cerr << "IPAInterface::" << name
+			     << "(): reported an error" << endl;
+			return TestFail;
+		}
+
+		return TestPass;
+	}
+
+	void queueFrameAction(unsigned int frame, const IPAOperationData &data)
+	{
+		frame_ = frame;
+		data_ = data;
+	}
+
+	std::shared_ptr<MediaDevice> media_;
+	std::unique_ptr<DeviceEnumerator> enumerator_;
+	V4L2Subdevice *subdev_;
+
+	IPAContextWrapper *wrapper_;
+	IPAOperationData data_;
+	unsigned int sequence_;
+	unsigned int frame_;
+	int fd_;
+};
+
+TEST_REGISTER(IPAWrappersTest)
diff --git a/test/ipa/meson.build b/test/ipa/meson.build
index c501fcf99aed..f925c50a085e 100644
--- a/test/ipa/meson.build
+++ b/test/ipa/meson.build
@@ -1,13 +1,14 @@ 
 ipa_test = [
     ['ipa_module_test',     'ipa_module_test.cpp'],
     ['ipa_interface_test',  'ipa_interface_test.cpp'],
+    ['ipa_wrappers_test',   'ipa_wrappers_test.cpp'],
 ]
 
 foreach t : ipa_test
     exe = executable(t[0], t[1],
                      dependencies : libcamera_dep,
-                     link_with : test_libraries,
-                     include_directories : test_includes_internal)
+                     link_with : [libipa, test_libraries],
+                     include_directories : [libipa_includes, test_includes_internal])
 
     test(t[0], exe, suite : 'ipa')
 endforeach