[libcamera-devel,v6,1/4] tests: Add IPADataSerializer test
diff mbox series

Message ID 20201224081713.41742-2-paul.elder@ideasonboard.com
State Superseded
Delegated to: Paul Elder
Headers show
Series
  • IPA isolation: Part 3: Tests and documentation
Related show

Commit Message

Paul Elder Dec. 24, 2020, 8:17 a.m. UTC
Test the IPADataSerializer for controls, vectors, maps, and PODs of
built-in types.

Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>

---
Change since v6:
- no longer need to initialize rpi ControlInfoMap
- no longer need to pass ControlInfoMap to the ControlList serializer

Changes in v5:
- use ControlInfoMap serializer instead of const ControlInfoMap
  serializer

Changes in v4:
- use RPi::controls instead RPi::Controls

Changes in v3:
- use re-namespaced RPi::Controls

New in v2
---
 .../ipa_data_serializer_test.cpp              | 464 ++++++++++++++++++
 test/serialization/meson.build                |   3 +-
 2 files changed, 466 insertions(+), 1 deletion(-)
 create mode 100644 test/serialization/ipa_data_serializer_test.cpp

Comments

Niklas Söderlund Dec. 29, 2020, 9:29 p.m. UTC | #1
Hi Paul,

Thanks for your test.

On 2020-12-24 17:17:10 +0900, Paul Elder wrote:
> Test the IPADataSerializer for controls, vectors, maps, and PODs of
> built-in types.
> 
> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>
> 
> ---
> Change since v6:
> - no longer need to initialize rpi ControlInfoMap
> - no longer need to pass ControlInfoMap to the ControlList serializer
> 
> Changes in v5:
> - use ControlInfoMap serializer instead of const ControlInfoMap
>   serializer
> 
> Changes in v4:
> - use RPi::controls instead RPi::Controls
> 
> Changes in v3:
> - use re-namespaced RPi::Controls
> 
> New in v2
> ---
>  .../ipa_data_serializer_test.cpp              | 464 ++++++++++++++++++
>  test/serialization/meson.build                |   3 +-
>  2 files changed, 466 insertions(+), 1 deletion(-)
>  create mode 100644 test/serialization/ipa_data_serializer_test.cpp
> 
> diff --git a/test/serialization/ipa_data_serializer_test.cpp b/test/serialization/ipa_data_serializer_test.cpp
> new file mode 100644
> index 00000000..914663ca
> --- /dev/null
> +++ b/test/serialization/ipa_data_serializer_test.cpp
> @@ -0,0 +1,464 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * Copyright (C) 2020, Google Inc.
> + *
> + * ipa_data_serializer_test.cpp - Test serializing/deserializing with IPADataSerializer
> + */
> +
> +#include <algorithm>
> +#include <fcntl.h>
> +#include <iostream>
> +#include <limits>
> +#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/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 "libcamera/internal/timer.h"
> +
> +#include "serialization_test.h"
> +#include "test.h"
> +
> +using namespace std;
> +using namespace libcamera;
> +
> +template<typename T>
> +bool isVectorEqual(vector<T> &vecA, vector<T> &vecB)
> +{
> +	if (vecA.size() != vecB.size())
> +		return false;
> +
> +	size_t len = vecA.size();
> +	for (unsigned int i = 0; i < len; i++)
> +		if (vecA[i] != vecB[i])
> +			goto nequal;
> +
> +	return true;
> +
> +nequal:
> +	cerr << "lhs: { ";
> +	for (const auto &value : vecA)
> +		cerr << value << ", ";
> +	cerr << "}" << endl;
> +
> +	cerr << "rhs: { ";
> +	for (const auto &value : vecB)
> +		cerr << value << ", ";
> +	cerr << "}" << endl;

I don't think it's needed to print the vectors if the test fails, as it 
should not happen after development is done right ;-) If you feel 
strongly about it I think all vector compare functions should print on 
fail.

> +
> +	return false;
> +}
> +
> +template<>
> +bool isVectorEqual<ControlInfoMap>(vector<ControlInfoMap> &vecA,
> +				   vector<ControlInfoMap> &vecB)
> +{
> +	if (vecA.size() != vecB.size())
> +		return false;
> +
> +	size_t len = vecA.size();
> +	for (unsigned int i = 0; i < len; i++)
> +		if (!SerializationTest::equals(vecA[i], vecB[i]))
> +			return false;
> +
> +	return true;
> +}
> +
> +template<typename K, typename V>
> +bool isMapEqual(map<K, V> &mapA, map<K, V> &mapB)
> +{
> +	if (mapA == mapB)
> +		return true;
> +
> +	cerr << "lhs: { ";
> +	for (const auto &value : mapA)
> +		cerr << value.first << " : " << value.second << ", ";
> +	cerr << "}" << endl;
> +
> +	cerr << "rhs: { ";
> +	for (const auto &value : mapB)
> +		cerr << value.first << " : " << value.second << ", ";
> +	cerr << "}" << endl;

Same comment as above about printing on failure.

> +
> +	return false;
> +}
> +
> +template<typename K, typename V>
> +bool isMapToCimEqual(map<K, V> &mapA, map<K, V> &mapB)
> +{
> +	bool isEqual = true;
> +
> +	auto itA = mapA.begin();
> +	auto itB = mapB.begin();
> +	while (true) {
> +		bool endA = (itA == mapA.end());
> +		bool endB = (itB == mapB.end());

I think you could check mapA.size() != mapB.size() before the loop to 
remove the need for these flags.

> +
> +		if (endA and endB)
> +			break;
> +
> +		if (!endA && (endB || itA->first < itB->first)) {
> +			cerr << "key: " << itA->first << " not in mapB" << endl;
> +			isEqual = false;
> +			itA++;
> +			continue;
> +		}
> +
> +		if (endA || itB->first < itA->first) {
> +			cerr << "key: " << itB->first << " not in mapA" << endl;
> +			isEqual = false;
> +			itB++;
> +			continue;
> +		}
> +
> +		if (!SerializationTest::equals(itA->second, itB->second)) {
> +			cerr << "key " << itA->first
> +			     << " has different values" << endl;
> +			isEqual = false;
> +		}
> +
> +		itA++;
> +		itB++;
> +	}
> +
> +	return isEqual;

Would it make sens to do redice this funnction to something like this 
(not tested)?

    if (mapA.size() != mapB.size())
        return false;

    for (cont auto &itA : mapA) {
        itB = mapB.find(itA->first);

        if (itB == mapB.end())
            return false;

        if (!SerializationTest::equals(itA->second, itB->second))
            return false;
    }

    return true;

> +}
> +
> +template<typename K, typename V>
> +bool isMapToVecEqual(map<K, V> &mapA, map<K, V> &mapB)
> +{
> +	if (mapA == mapB)
> +		return true;
> +
> +	cerr << "lhs: { ";
> +	for (const auto &value : mapA) {
> +		cerr << value.first << " : { ";
> +		for (const auto &v : value.second)
> +			cerr << v << ", ";
> +		cerr << "}" << endl;
> +	}
> +	cerr << "}" << endl;
> +
> +	cerr << "rhs: { ";
> +	for (const auto &value : mapB) {
> +		cerr << value.first << " : { ";
> +		for (const auto &v : value.second)
> +			cerr << v << ", ";
> +		cerr << "}" << endl;
> +	}
> +	cerr << "}" << endl;

Print or no print on fail ;-)

> +
> +	return false;
> +}
> +
> +class IPADataSerializerTest : public CameraTest, public Test
> +{
> +public:
> +	IPADataSerializerTest()
> +		: CameraTest("platform/vimc.0 Sensor B")
> +	{
> +	}
> +
> +protected:
> +	int init() override
> +	{
> +		return status_;
> +	}
> +
> +	int run() override
> +	{
> +		int finalRet = TestPass;
> +		int ret;
> +
> +		ret = testControls();
> +		if (ret != TestPass)
> +			finalRet = ret;
> +
> +		ret = testVector();
> +		if (ret != TestPass)
> +			finalRet = ret;
> +
> +		ret = testMap();
> +		if (ret != TestPass)
> +			finalRet = ret;
> +
> +		ret = testPod();
> +		if (ret != TestPass)
> +			finalRet = ret;
> +
> +		return finalRet;

I think it's OK to fail as early as possible and just drop the finalRet 
variable.

> +	}
> +
> +private:
> +	ControlList generateControlListA()
> +	{
> +		/* Create a control list with three controls. */
> +		const ControlInfoMap &infoMap = camera_->controls();
> +		ControlList list(infoMap);

nit: You could post the infoMap as an argument as you fetch it just 
before the only caller.

> +
> +		list.set(controls::Brightness, 0.5f);
> +		list.set(controls::Contrast, 1.2f);
> +		list.set(controls::Saturation, 0.2f);
> +
> +		return list;
> +	}
> +
> +	int testControls()
> +	{
> +		ControlSerializer cs;
> +
> +		const ControlInfoMap &infoMap = camera_->controls();
> +		ControlList list = generateControlListA();
> +
> +		vector<uint8_t> infoMapBuf;
> +		tie(infoMapBuf, ignore) =
> +			IPADataSerializer<ControlInfoMap>::serialize(infoMap, &cs);
> +
> +		vector<uint8_t> listBuf;
> +		tie(listBuf, ignore) =
> +			IPADataSerializer<ControlList>::serialize(list, &cs);
> +
> +		const ControlInfoMap infoMapOut =
> +			IPADataSerializer<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;
> +	}
> +
> +	int testVector()
> +	{
> +
> +#define TEST_VEC_SERDES(type, vec, cs)					\
> +tie(buf, fds) = IPADataSerializer<vector<type>>::serialize(vec, cs);	\
> +vector<type> vec##Out =							\
> +	IPADataSerializer<vector<type>>::deserialize(buf, fds, cs);	\
> +ret = isVectorEqual<type>(vec, vec##Out);				\
> +if (!ret) {								\
> +	cerr << "Deserialized vector " << #vec << " doesn't match original" << endl;\
> +	finalRet = TestFail;						\

I would fail early here with a 'return TestFail'.

> +}
> +
> +		ControlSerializer cs;
> +
> +		/*
> +		 * We don't test FileDescriptor serdes because it dup()s, so we
> +		 * can't check for equality.
> +		 */
> +		vector<uint8_t>  vecUint8  = { 1, 2, 3, 4, 5, 6 };
> +		vector<uint16_t> vecUint16 = { 1, 2, 3, 4, 5, 6 };
> +		vector<uint32_t> vecUint32 = { 1, 2, 3, 4, 5, 6 };
> +		vector<uint64_t> vecUint64 = { 1, 2, 3, 4, 5, 6 };
> +		vector<int8_t>   vecInt8   = { 1, 2, 3, -4, 5, -6 };
> +		vector<int16_t>  vecInt16  = { 1, 2, 3, -4, 5, -6 };
> +		vector<int32_t>  vecInt32  = { 1, 2, 3, -4, 5, -6 };
> +		vector<int64_t>  vecInt64  = { 1, 2, 3, -4, 5, -6 };
> +		vector<float>    vecFloat  = { 1.1, 2.2, 3.3, -4.4, 5.5, -6.6 };
> +		vector<double>   vecDouble = { 1.1, 2.2, 3.3, -4.4, 5.5, -6.6 };
> +		vector<bool>     vecBool   = { true, true, false, false, true, false };
> +		vector<string>   vecString = { "foo", "bar", "baz" };
> +		vector<ControlInfoMap> vecControlInfoMap = {
> +			camera_->controls(),
> +			RPi::Controls,
> +		};
> +
> +		vector<uint8_t> buf;
> +		vector<int32_t> fds;
> +		int finalRet = TestPass;
> +		int ret;
> +
> +		TEST_VEC_SERDES(uint8_t,  vecUint8,  nullptr);
> +		TEST_VEC_SERDES(uint16_t, vecUint16, nullptr);
> +		TEST_VEC_SERDES(uint32_t, vecUint32, nullptr);
> +		TEST_VEC_SERDES(uint64_t, vecUint64, nullptr);
> +		TEST_VEC_SERDES(int8_t,   vecInt8,   nullptr);
> +		TEST_VEC_SERDES(int16_t,  vecInt16,  nullptr);
> +		TEST_VEC_SERDES(int32_t,  vecInt32,  nullptr);
> +		TEST_VEC_SERDES(int64_t,  vecInt64,  nullptr);
> +		TEST_VEC_SERDES(float,    vecFloat,  nullptr);
> +		TEST_VEC_SERDES(double,   vecDouble, nullptr);
> +		TEST_VEC_SERDES(bool,     vecBool,   nullptr);
> +		TEST_VEC_SERDES(string,   vecString, nullptr);
> +		TEST_VEC_SERDES(ControlInfoMap, vecControlInfoMap, &cs);
> +
> +		return finalRet;
> +	}
> +
> +	int testMap()
> +	{
> +
> +#define TEST_MAP_SERDES(ktype, vtype, m, cs)				\
> +tie(buf, fds) = IPADataSerializer<map<ktype, vtype>>::serialize(m, cs);	\
> +map<ktype, vtype> m##Out =						\
> +	IPADataSerializer<map<ktype, vtype>>::deserialize(buf, fds, cs);\
> +ret = isMapEqual<ktype, vtype>(m, m##Out);				\
> +if (!ret) {								\
> +	cerr << "Deserialized map " << #m << " doesn't match original" << endl;\
> +	finalRet = TestFail;						\

Same as above, fail early IMHO.

> +}
> +
> +#define TEST_MAP_CIM_SERDES(ktype, vtype, m, cs)			\
> +tie(buf, fds) = IPADataSerializer<map<ktype, vtype>>::serialize(m, cs);	\
> +map<ktype, vtype> m##Out =						\
> +	IPADataSerializer<map<ktype, vtype>>::deserialize(buf, fds, cs);\
> +ret = isMapToCimEqual<ktype, vtype>(m, m##Out);				\
> +if (!ret) {								\
> +	cerr << "Deserialized map " << #m << " doesn't match original" << endl;\
> +	finalRet = TestFail;						\

Ditto.

> +}
> +
> +#define TEST_MAP_VEC_SERDES(ktype, vtype, m, cs)			\
> +tie(buf, fds) = IPADataSerializer<map<ktype, vtype>>::serialize(m, cs);	\
> +map<ktype, vtype> m##Out =						\
> +	IPADataSerializer<map<ktype, vtype>>::deserialize(buf, fds, cs);\
> +ret = isMapToVecEqual<ktype, vtype>(m, m##Out);				\
> +if (!ret) {								\
> +	cerr << "Deserialized map " << #m << " doesn't match original" << endl;\
> +	finalRet = TestFail;						\

Ditto.

> +}
> +
> +		ControlSerializer cs;
> +
> +		/*
> +		 * Realistically, only string and integral keys.
> +		 * Test simple, complex, and nested compound value.
> +		 */
> +		map<uint64_t, string> mapUintStr =
> +			{ {101, "foo"}, {102, "bar"}, {103, "baz"} };
> +		map<int64_t, string> mapIntStr =
> +			{ {101, "foo"}, {-102, "bar"}, {-103, "baz"} };
> +		map<string, string> mapStrStr =
> +			{ {"a", "foo"}, {"b", "bar"}, {"c", "baz"} };
> +		map<uint64_t, ControlInfoMap> mapUintCIM =
> +			{ {201, camera_->controls()}, {202, RPi::Controls} };
> +		map<int64_t, ControlInfoMap> mapIntCIM =
> +			{ {201, camera_->controls()}, {-202, RPi::Controls} };
> +		map<string, ControlInfoMap> mapStrCIM =
> +			{ {"a", camera_->controls()}, {"b", RPi::Controls} };
> +		map<uint64_t, vector<uint8_t>> mapUintBVec =
> +			{ {301, { 1, 2, 3 }}, {302, {4, 5, 6}}, {303, {7, 8, 9}} };
> +		map<int64_t, vector<uint8_t>> mapIntBVec =
> +			{ {301, { 1, 2, 3 }}, {-302, {4, 5, 6}}, {-303, {7, 8, 9}} };
> +		map<string, vector<uint8_t>> mapStrBVec =
> +			{ {"a", { 1, 2, 3 }}, {"b", {4, 5, 6}}, {"c", {7, 8, 9}} };
> +
> +		vector<uint8_t> buf;
> +		vector<int32_t> fds;
> +		int finalRet = TestPass;
> +		int ret;
> +
> +		TEST_MAP_SERDES(uint64_t, string,          mapUintStr, nullptr);
> +		TEST_MAP_SERDES(int64_t,  string,          mapIntStr,  nullptr);
> +		TEST_MAP_SERDES(string,   string,          mapStrStr,  nullptr);
> +		TEST_MAP_CIM_SERDES(uint64_t, ControlInfoMap,  mapUintCIM, &cs);
> +		TEST_MAP_CIM_SERDES(int64_t,  ControlInfoMap,  mapIntCIM,  &cs);
> +		TEST_MAP_CIM_SERDES(string,   ControlInfoMap,  mapStrCIM,  &cs);
> +		TEST_MAP_VEC_SERDES(uint64_t, vector<uint8_t>, mapUintBVec, nullptr);
> +		TEST_MAP_VEC_SERDES(int64_t,  vector<uint8_t>, mapIntBVec,  nullptr);
> +		TEST_MAP_VEC_SERDES(string,   vector<uint8_t>, mapStrBVec,  nullptr);
> +
> +		return finalRet;
> +	}
> +
> +	int testPod()
> +	{
> +
> +#define TEST_POD_SERDES(type, var)				\
> +tie(buf, fds) = IPADataSerializer<type>::serialize(var);	\
> +type var##Out =							\
> +	IPADataSerializer<type>::deserialize(buf, fds);		\
> +ret = (var == var##Out);					\
> +if (!ret) {							\
> +	cerr << "Deserialized " << #var << " as " << var##Out	\
> +	     << ", expected " << var << endl;			\
> +	finalRet = TestFail;					\
> +}
> +
> +		uint32_t u32min = numeric_limits<uint32_t>::min();
> +		uint32_t u32max = numeric_limits<uint32_t>::max();
> +		uint32_t u32one = 1;
> +		int32_t  i32min = numeric_limits<int32_t>::min();
> +		int32_t  i32max = numeric_limits<int32_t>::max();
> +		int32_t  i32one = 1;
> +
> +		uint64_t u64min = numeric_limits<uint64_t>::min();
> +		uint64_t u64max = numeric_limits<uint64_t>::max();
> +		uint64_t u64one = 1;
> +		int64_t  i64min = numeric_limits<int64_t>::min();
> +		int64_t  i64max = numeric_limits<int64_t>::max();
> +		int64_t  i64one = 1;
> +
> +		float  flow = numeric_limits<float>::lowest();
> +		float  fmin = numeric_limits<float>::min();
> +		float  fmax = numeric_limits<float>::max();
> +		float  falmostOne = 1 + 1.0e-37;
> +		double dlow = numeric_limits<double>::lowest();
> +		double dmin = numeric_limits<double>::min();
> +		double dmax = numeric_limits<double>::max();
> +		double dalmostOne = 1 + 1.0e-307;
> +
> +		bool t = true;
> +		bool f = false;
> +
> +		stringstream ss;
> +		for (unsigned int i = 0; i < (1 << 21); i++)
> +			ss << "0123456789";
> +
> +		string strLong = ss.str();
> +		string strEmpty = "";
> +
> +		vector<uint8_t> buf;
> +		vector<int32_t> fds;
> +		int finalRet = TestPass;
> +		int ret;
> +
> +		TEST_POD_SERDES(uint32_t, u32min);
> +		TEST_POD_SERDES(uint32_t, u32max);
> +		TEST_POD_SERDES(uint32_t, u32one);
> +		TEST_POD_SERDES(int32_t,  i32min);
> +		TEST_POD_SERDES(int32_t,  i32max);
> +		TEST_POD_SERDES(int32_t,  i32one);
> +		TEST_POD_SERDES(uint64_t, u64min);
> +		TEST_POD_SERDES(uint64_t, u64max);
> +		TEST_POD_SERDES(uint64_t, u64one);
> +		TEST_POD_SERDES(int64_t,  i64min);
> +		TEST_POD_SERDES(int64_t,  i64max);
> +		TEST_POD_SERDES(int64_t,  i64one);
> +		TEST_POD_SERDES(float,    flow);
> +		TEST_POD_SERDES(float,    fmin);
> +		TEST_POD_SERDES(float,    fmax);
> +		TEST_POD_SERDES(float,    falmostOne);
> +		TEST_POD_SERDES(double,   dlow);
> +		TEST_POD_SERDES(double,   dmin);
> +		TEST_POD_SERDES(double,   dmax);
> +		TEST_POD_SERDES(double,   dalmostOne);
> +		TEST_POD_SERDES(bool,     t);
> +		TEST_POD_SERDES(bool,     f);
> +		TEST_POD_SERDES(string,   strLong);
> +		TEST_POD_SERDES(string,   strEmpty);
> +
> +		return finalRet;
> +	}
> +};
> +
> +TEST_REGISTER(IPADataSerializerTest)
> diff --git a/test/serialization/meson.build b/test/serialization/meson.build
> index a9d9cbcb..c140a31c 100644
> --- a/test/serialization/meson.build
> +++ b/test/serialization/meson.build
> @@ -1,7 +1,8 @@
>  # SPDX-License-Identifier: CC0-1.0
>  
>  serialization_tests = [
> -    [ 'control_serialization',    'control_serialization.cpp' ],
> +    [ 'control_serialization',     'control_serialization.cpp' ],
> +    [ 'ipa_data_serializer_test',  'ipa_data_serializer_test.cpp' ],
>  ]
>  
>  foreach t : serialization_tests
> -- 
> 2.27.0
> 
> _______________________________________________
> libcamera-devel mailing list
> libcamera-devel@lists.libcamera.org
> https://lists.libcamera.org/listinfo/libcamera-devel

Patch
diff mbox series

diff --git a/test/serialization/ipa_data_serializer_test.cpp b/test/serialization/ipa_data_serializer_test.cpp
new file mode 100644
index 00000000..914663ca
--- /dev/null
+++ b/test/serialization/ipa_data_serializer_test.cpp
@@ -0,0 +1,464 @@ 
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2020, Google Inc.
+ *
+ * ipa_data_serializer_test.cpp - Test serializing/deserializing with IPADataSerializer
+ */
+
+#include <algorithm>
+#include <fcntl.h>
+#include <iostream>
+#include <limits>
+#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/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 "libcamera/internal/timer.h"
+
+#include "serialization_test.h"
+#include "test.h"
+
+using namespace std;
+using namespace libcamera;
+
+template<typename T>
+bool isVectorEqual(vector<T> &vecA, vector<T> &vecB)
+{
+	if (vecA.size() != vecB.size())
+		return false;
+
+	size_t len = vecA.size();
+	for (unsigned int i = 0; i < len; i++)
+		if (vecA[i] != vecB[i])
+			goto nequal;
+
+	return true;
+
+nequal:
+	cerr << "lhs: { ";
+	for (const auto &value : vecA)
+		cerr << value << ", ";
+	cerr << "}" << endl;
+
+	cerr << "rhs: { ";
+	for (const auto &value : vecB)
+		cerr << value << ", ";
+	cerr << "}" << endl;
+
+	return false;
+}
+
+template<>
+bool isVectorEqual<ControlInfoMap>(vector<ControlInfoMap> &vecA,
+				   vector<ControlInfoMap> &vecB)
+{
+	if (vecA.size() != vecB.size())
+		return false;
+
+	size_t len = vecA.size();
+	for (unsigned int i = 0; i < len; i++)
+		if (!SerializationTest::equals(vecA[i], vecB[i]))
+			return false;
+
+	return true;
+}
+
+template<typename K, typename V>
+bool isMapEqual(map<K, V> &mapA, map<K, V> &mapB)
+{
+	if (mapA == mapB)
+		return true;
+
+	cerr << "lhs: { ";
+	for (const auto &value : mapA)
+		cerr << value.first << " : " << value.second << ", ";
+	cerr << "}" << endl;
+
+	cerr << "rhs: { ";
+	for (const auto &value : mapB)
+		cerr << value.first << " : " << value.second << ", ";
+	cerr << "}" << endl;
+
+	return false;
+}
+
+template<typename K, typename V>
+bool isMapToCimEqual(map<K, V> &mapA, map<K, V> &mapB)
+{
+	bool isEqual = true;
+
+	auto itA = mapA.begin();
+	auto itB = mapB.begin();
+	while (true) {
+		bool endA = (itA == mapA.end());
+		bool endB = (itB == mapB.end());
+
+		if (endA and endB)
+			break;
+
+		if (!endA && (endB || itA->first < itB->first)) {
+			cerr << "key: " << itA->first << " not in mapB" << endl;
+			isEqual = false;
+			itA++;
+			continue;
+		}
+
+		if (endA || itB->first < itA->first) {
+			cerr << "key: " << itB->first << " not in mapA" << endl;
+			isEqual = false;
+			itB++;
+			continue;
+		}
+
+		if (!SerializationTest::equals(itA->second, itB->second)) {
+			cerr << "key " << itA->first
+			     << " has different values" << endl;
+			isEqual = false;
+		}
+
+		itA++;
+		itB++;
+	}
+
+	return isEqual;
+}
+
+template<typename K, typename V>
+bool isMapToVecEqual(map<K, V> &mapA, map<K, V> &mapB)
+{
+	if (mapA == mapB)
+		return true;
+
+	cerr << "lhs: { ";
+	for (const auto &value : mapA) {
+		cerr << value.first << " : { ";
+		for (const auto &v : value.second)
+			cerr << v << ", ";
+		cerr << "}" << endl;
+	}
+	cerr << "}" << endl;
+
+	cerr << "rhs: { ";
+	for (const auto &value : mapB) {
+		cerr << value.first << " : { ";
+		for (const auto &v : value.second)
+			cerr << v << ", ";
+		cerr << "}" << endl;
+	}
+	cerr << "}" << endl;
+
+	return false;
+}
+
+class IPADataSerializerTest : public CameraTest, public Test
+{
+public:
+	IPADataSerializerTest()
+		: CameraTest("platform/vimc.0 Sensor B")
+	{
+	}
+
+protected:
+	int init() override
+	{
+		return status_;
+	}
+
+	int run() override
+	{
+		int finalRet = TestPass;
+		int ret;
+
+		ret = testControls();
+		if (ret != TestPass)
+			finalRet = ret;
+
+		ret = testVector();
+		if (ret != TestPass)
+			finalRet = ret;
+
+		ret = testMap();
+		if (ret != TestPass)
+			finalRet = ret;
+
+		ret = testPod();
+		if (ret != TestPass)
+			finalRet = ret;
+
+		return finalRet;
+	}
+
+private:
+	ControlList generateControlListA()
+	{
+		/* 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);
+
+		return list;
+	}
+
+	int testControls()
+	{
+		ControlSerializer cs;
+
+		const ControlInfoMap &infoMap = camera_->controls();
+		ControlList list = generateControlListA();
+
+		vector<uint8_t> infoMapBuf;
+		tie(infoMapBuf, ignore) =
+			IPADataSerializer<ControlInfoMap>::serialize(infoMap, &cs);
+
+		vector<uint8_t> listBuf;
+		tie(listBuf, ignore) =
+			IPADataSerializer<ControlList>::serialize(list, &cs);
+
+		const ControlInfoMap infoMapOut =
+			IPADataSerializer<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;
+	}
+
+	int testVector()
+	{
+
+#define TEST_VEC_SERDES(type, vec, cs)					\
+tie(buf, fds) = IPADataSerializer<vector<type>>::serialize(vec, cs);	\
+vector<type> vec##Out =							\
+	IPADataSerializer<vector<type>>::deserialize(buf, fds, cs);	\
+ret = isVectorEqual<type>(vec, vec##Out);				\
+if (!ret) {								\
+	cerr << "Deserialized vector " << #vec << " doesn't match original" << endl;\
+	finalRet = TestFail;						\
+}
+
+		ControlSerializer cs;
+
+		/*
+		 * We don't test FileDescriptor serdes because it dup()s, so we
+		 * can't check for equality.
+		 */
+		vector<uint8_t>  vecUint8  = { 1, 2, 3, 4, 5, 6 };
+		vector<uint16_t> vecUint16 = { 1, 2, 3, 4, 5, 6 };
+		vector<uint32_t> vecUint32 = { 1, 2, 3, 4, 5, 6 };
+		vector<uint64_t> vecUint64 = { 1, 2, 3, 4, 5, 6 };
+		vector<int8_t>   vecInt8   = { 1, 2, 3, -4, 5, -6 };
+		vector<int16_t>  vecInt16  = { 1, 2, 3, -4, 5, -6 };
+		vector<int32_t>  vecInt32  = { 1, 2, 3, -4, 5, -6 };
+		vector<int64_t>  vecInt64  = { 1, 2, 3, -4, 5, -6 };
+		vector<float>    vecFloat  = { 1.1, 2.2, 3.3, -4.4, 5.5, -6.6 };
+		vector<double>   vecDouble = { 1.1, 2.2, 3.3, -4.4, 5.5, -6.6 };
+		vector<bool>     vecBool   = { true, true, false, false, true, false };
+		vector<string>   vecString = { "foo", "bar", "baz" };
+		vector<ControlInfoMap> vecControlInfoMap = {
+			camera_->controls(),
+			RPi::Controls,
+		};
+
+		vector<uint8_t> buf;
+		vector<int32_t> fds;
+		int finalRet = TestPass;
+		int ret;
+
+		TEST_VEC_SERDES(uint8_t,  vecUint8,  nullptr);
+		TEST_VEC_SERDES(uint16_t, vecUint16, nullptr);
+		TEST_VEC_SERDES(uint32_t, vecUint32, nullptr);
+		TEST_VEC_SERDES(uint64_t, vecUint64, nullptr);
+		TEST_VEC_SERDES(int8_t,   vecInt8,   nullptr);
+		TEST_VEC_SERDES(int16_t,  vecInt16,  nullptr);
+		TEST_VEC_SERDES(int32_t,  vecInt32,  nullptr);
+		TEST_VEC_SERDES(int64_t,  vecInt64,  nullptr);
+		TEST_VEC_SERDES(float,    vecFloat,  nullptr);
+		TEST_VEC_SERDES(double,   vecDouble, nullptr);
+		TEST_VEC_SERDES(bool,     vecBool,   nullptr);
+		TEST_VEC_SERDES(string,   vecString, nullptr);
+		TEST_VEC_SERDES(ControlInfoMap, vecControlInfoMap, &cs);
+
+		return finalRet;
+	}
+
+	int testMap()
+	{
+
+#define TEST_MAP_SERDES(ktype, vtype, m, cs)				\
+tie(buf, fds) = IPADataSerializer<map<ktype, vtype>>::serialize(m, cs);	\
+map<ktype, vtype> m##Out =						\
+	IPADataSerializer<map<ktype, vtype>>::deserialize(buf, fds, cs);\
+ret = isMapEqual<ktype, vtype>(m, m##Out);				\
+if (!ret) {								\
+	cerr << "Deserialized map " << #m << " doesn't match original" << endl;\
+	finalRet = TestFail;						\
+}
+
+#define TEST_MAP_CIM_SERDES(ktype, vtype, m, cs)			\
+tie(buf, fds) = IPADataSerializer<map<ktype, vtype>>::serialize(m, cs);	\
+map<ktype, vtype> m##Out =						\
+	IPADataSerializer<map<ktype, vtype>>::deserialize(buf, fds, cs);\
+ret = isMapToCimEqual<ktype, vtype>(m, m##Out);				\
+if (!ret) {								\
+	cerr << "Deserialized map " << #m << " doesn't match original" << endl;\
+	finalRet = TestFail;						\
+}
+
+#define TEST_MAP_VEC_SERDES(ktype, vtype, m, cs)			\
+tie(buf, fds) = IPADataSerializer<map<ktype, vtype>>::serialize(m, cs);	\
+map<ktype, vtype> m##Out =						\
+	IPADataSerializer<map<ktype, vtype>>::deserialize(buf, fds, cs);\
+ret = isMapToVecEqual<ktype, vtype>(m, m##Out);				\
+if (!ret) {								\
+	cerr << "Deserialized map " << #m << " doesn't match original" << endl;\
+	finalRet = TestFail;						\
+}
+
+		ControlSerializer cs;
+
+		/*
+		 * Realistically, only string and integral keys.
+		 * Test simple, complex, and nested compound value.
+		 */
+		map<uint64_t, string> mapUintStr =
+			{ {101, "foo"}, {102, "bar"}, {103, "baz"} };
+		map<int64_t, string> mapIntStr =
+			{ {101, "foo"}, {-102, "bar"}, {-103, "baz"} };
+		map<string, string> mapStrStr =
+			{ {"a", "foo"}, {"b", "bar"}, {"c", "baz"} };
+		map<uint64_t, ControlInfoMap> mapUintCIM =
+			{ {201, camera_->controls()}, {202, RPi::Controls} };
+		map<int64_t, ControlInfoMap> mapIntCIM =
+			{ {201, camera_->controls()}, {-202, RPi::Controls} };
+		map<string, ControlInfoMap> mapStrCIM =
+			{ {"a", camera_->controls()}, {"b", RPi::Controls} };
+		map<uint64_t, vector<uint8_t>> mapUintBVec =
+			{ {301, { 1, 2, 3 }}, {302, {4, 5, 6}}, {303, {7, 8, 9}} };
+		map<int64_t, vector<uint8_t>> mapIntBVec =
+			{ {301, { 1, 2, 3 }}, {-302, {4, 5, 6}}, {-303, {7, 8, 9}} };
+		map<string, vector<uint8_t>> mapStrBVec =
+			{ {"a", { 1, 2, 3 }}, {"b", {4, 5, 6}}, {"c", {7, 8, 9}} };
+
+		vector<uint8_t> buf;
+		vector<int32_t> fds;
+		int finalRet = TestPass;
+		int ret;
+
+		TEST_MAP_SERDES(uint64_t, string,          mapUintStr, nullptr);
+		TEST_MAP_SERDES(int64_t,  string,          mapIntStr,  nullptr);
+		TEST_MAP_SERDES(string,   string,          mapStrStr,  nullptr);
+		TEST_MAP_CIM_SERDES(uint64_t, ControlInfoMap,  mapUintCIM, &cs);
+		TEST_MAP_CIM_SERDES(int64_t,  ControlInfoMap,  mapIntCIM,  &cs);
+		TEST_MAP_CIM_SERDES(string,   ControlInfoMap,  mapStrCIM,  &cs);
+		TEST_MAP_VEC_SERDES(uint64_t, vector<uint8_t>, mapUintBVec, nullptr);
+		TEST_MAP_VEC_SERDES(int64_t,  vector<uint8_t>, mapIntBVec,  nullptr);
+		TEST_MAP_VEC_SERDES(string,   vector<uint8_t>, mapStrBVec,  nullptr);
+
+		return finalRet;
+	}
+
+	int testPod()
+	{
+
+#define TEST_POD_SERDES(type, var)				\
+tie(buf, fds) = IPADataSerializer<type>::serialize(var);	\
+type var##Out =							\
+	IPADataSerializer<type>::deserialize(buf, fds);		\
+ret = (var == var##Out);					\
+if (!ret) {							\
+	cerr << "Deserialized " << #var << " as " << var##Out	\
+	     << ", expected " << var << endl;			\
+	finalRet = TestFail;					\
+}
+
+		uint32_t u32min = numeric_limits<uint32_t>::min();
+		uint32_t u32max = numeric_limits<uint32_t>::max();
+		uint32_t u32one = 1;
+		int32_t  i32min = numeric_limits<int32_t>::min();
+		int32_t  i32max = numeric_limits<int32_t>::max();
+		int32_t  i32one = 1;
+
+		uint64_t u64min = numeric_limits<uint64_t>::min();
+		uint64_t u64max = numeric_limits<uint64_t>::max();
+		uint64_t u64one = 1;
+		int64_t  i64min = numeric_limits<int64_t>::min();
+		int64_t  i64max = numeric_limits<int64_t>::max();
+		int64_t  i64one = 1;
+
+		float  flow = numeric_limits<float>::lowest();
+		float  fmin = numeric_limits<float>::min();
+		float  fmax = numeric_limits<float>::max();
+		float  falmostOne = 1 + 1.0e-37;
+		double dlow = numeric_limits<double>::lowest();
+		double dmin = numeric_limits<double>::min();
+		double dmax = numeric_limits<double>::max();
+		double dalmostOne = 1 + 1.0e-307;
+
+		bool t = true;
+		bool f = false;
+
+		stringstream ss;
+		for (unsigned int i = 0; i < (1 << 21); i++)
+			ss << "0123456789";
+
+		string strLong = ss.str();
+		string strEmpty = "";
+
+		vector<uint8_t> buf;
+		vector<int32_t> fds;
+		int finalRet = TestPass;
+		int ret;
+
+		TEST_POD_SERDES(uint32_t, u32min);
+		TEST_POD_SERDES(uint32_t, u32max);
+		TEST_POD_SERDES(uint32_t, u32one);
+		TEST_POD_SERDES(int32_t,  i32min);
+		TEST_POD_SERDES(int32_t,  i32max);
+		TEST_POD_SERDES(int32_t,  i32one);
+		TEST_POD_SERDES(uint64_t, u64min);
+		TEST_POD_SERDES(uint64_t, u64max);
+		TEST_POD_SERDES(uint64_t, u64one);
+		TEST_POD_SERDES(int64_t,  i64min);
+		TEST_POD_SERDES(int64_t,  i64max);
+		TEST_POD_SERDES(int64_t,  i64one);
+		TEST_POD_SERDES(float,    flow);
+		TEST_POD_SERDES(float,    fmin);
+		TEST_POD_SERDES(float,    fmax);
+		TEST_POD_SERDES(float,    falmostOne);
+		TEST_POD_SERDES(double,   dlow);
+		TEST_POD_SERDES(double,   dmin);
+		TEST_POD_SERDES(double,   dmax);
+		TEST_POD_SERDES(double,   dalmostOne);
+		TEST_POD_SERDES(bool,     t);
+		TEST_POD_SERDES(bool,     f);
+		TEST_POD_SERDES(string,   strLong);
+		TEST_POD_SERDES(string,   strEmpty);
+
+		return finalRet;
+	}
+};
+
+TEST_REGISTER(IPADataSerializerTest)
diff --git a/test/serialization/meson.build b/test/serialization/meson.build
index a9d9cbcb..c140a31c 100644
--- a/test/serialization/meson.build
+++ b/test/serialization/meson.build
@@ -1,7 +1,8 @@ 
 # SPDX-License-Identifier: CC0-1.0
 
 serialization_tests = [
-    [ 'control_serialization',    'control_serialization.cpp' ],
+    [ 'control_serialization',     'control_serialization.cpp' ],
+    [ 'ipa_data_serializer_test',  'ipa_data_serializer_test.cpp' ],
 ]
 
 foreach t : serialization_tests