From edad4c40c3f97ffbe2f3d7edda0a09e3e5136a40 Mon Sep 17 00:00:00 2001
From: Daniel Agar <daniel@agar.ca>
Date: Fri, 22 Feb 2019 13:34:23 -0500
Subject: [PATCH] containers add IntrusiveQueue and testing

---
 platforms/posix/cmake/sitl_tests.cmake       |   1 +
 src/include/containers/IntrusiveQueue.hpp    | 115 ++++++++++
 src/systemcmds/tests/CMakeLists.txt          |   1 +
 src/systemcmds/tests/test_IntrusiveQueue.cpp | 214 +++++++++++++++++++
 src/systemcmds/tests/tests_main.c            |   1 +
 src/systemcmds/tests/tests_main.h            |   1 +
 6 files changed, 333 insertions(+)
 create mode 100644 src/include/containers/IntrusiveQueue.hpp
 create mode 100644 src/systemcmds/tests/test_IntrusiveQueue.cpp

diff --git a/platforms/posix/cmake/sitl_tests.cmake b/platforms/posix/cmake/sitl_tests.cmake
index 1b329e6e05..fa537217bd 100644
--- a/platforms/posix/cmake/sitl_tests.cmake
+++ b/platforms/posix/cmake/sitl_tests.cmake
@@ -17,6 +17,7 @@ set(tests
 	hrt
 	hysteresis
 	int
+	IntrusiveQueue
 	List
 	mathlib
 	matrix
diff --git a/src/include/containers/IntrusiveQueue.hpp b/src/include/containers/IntrusiveQueue.hpp
new file mode 100644
index 0000000000..100017cd29
--- /dev/null
+++ b/src/include/containers/IntrusiveQueue.hpp
@@ -0,0 +1,115 @@
+/****************************************************************************
+ *
+ *   Copyright (C) 2019 PX4 Development Team. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ * 3. Neither the name PX4 nor the names of its contributors may be
+ *    used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ ****************************************************************************/
+
+#pragma once
+
+#include <stdlib.h>
+
+template<class T>
+class IntrusiveQueue
+{
+public:
+
+	bool empty() const { return _head == nullptr; }
+
+	T front() const { return _head; }
+	T back() const { return _tail; }
+
+	size_t size() const
+	{
+		size_t sz = 0;
+
+		for (auto node = front(); node != nullptr; node = node->next_intrusive_queue_node()) {
+			sz++;
+		}
+
+		return sz;
+	}
+
+	void push(T newNode)
+	{
+		// error, node already queued or already inserted
+		if ((newNode->next_intrusive_queue_node() != nullptr) || (newNode == _tail)) {
+			return;
+		}
+
+		if (_head == nullptr) {
+			_head = newNode;
+		}
+
+		if (_tail != nullptr) {
+			_tail->set_next_intrusive_queue_node(newNode);
+		}
+
+		_tail = newNode;
+	}
+
+	T pop()
+	{
+		T ret = _head;
+
+		if (!empty()) {
+			if (_head != _tail) {
+				_head = _head->next_intrusive_queue_node();
+
+			} else {
+				// only one item left
+				_head = nullptr;
+				_tail = nullptr;
+			}
+
+			// clear next in popped (in might be re-inserted later)
+			ret->set_next_intrusive_queue_node(nullptr);
+		}
+
+		return ret;
+	}
+
+private:
+
+	T _head{nullptr};
+	T _tail{nullptr};
+
+};
+
+template<class T>
+class IntrusiveQueueNode
+{
+private:
+	friend IntrusiveQueue<T>;
+
+	T next_intrusive_queue_node() const { return _next_intrusive_queue_node; }
+	void set_next_intrusive_queue_node(T new_next) { _next_intrusive_queue_node = new_next; }
+
+	T _next_intrusive_queue_node{nullptr};
+};
diff --git a/src/systemcmds/tests/CMakeLists.txt b/src/systemcmds/tests/CMakeLists.txt
index d62920cb75..01d1eccaef 100644
--- a/src/systemcmds/tests/CMakeLists.txt
+++ b/src/systemcmds/tests/CMakeLists.txt
@@ -46,6 +46,7 @@ set(srcs
 	test_hrt.cpp
 	test_hysteresis.cpp
 	test_int.cpp
+	test_IntrusiveQueue.cpp
 	test_jig_voltages.c
 	test_led.c
 	test_List.cpp
diff --git a/src/systemcmds/tests/test_IntrusiveQueue.cpp b/src/systemcmds/tests/test_IntrusiveQueue.cpp
new file mode 100644
index 0000000000..d82f7607f4
--- /dev/null
+++ b/src/systemcmds/tests/test_IntrusiveQueue.cpp
@@ -0,0 +1,214 @@
+/****************************************************************************
+ *
+ *  Copyright (C) 2019 PX4 Development Team. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ * 3. Neither the name PX4 nor the names of its contributors may be
+ *    used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ ****************************************************************************/
+
+#include <unit_test.h>
+#include <containers/IntrusiveQueue.hpp>
+#include <float.h>
+#include <math.h>
+
+class testContainer : public IntrusiveQueueNode<testContainer *>
+{
+public:
+	int i{0};
+};
+
+class IntrusiveQueueTest : public UnitTest
+{
+public:
+	virtual bool run_tests();
+
+	bool test_push();
+	bool test_pop();
+	bool test_push_duplicate();
+
+};
+
+bool IntrusiveQueueTest::run_tests()
+{
+	ut_run_test(test_push);
+	ut_run_test(test_pop);
+	ut_run_test(test_push_duplicate);
+
+	return (_tests_failed == 0);
+}
+
+bool IntrusiveQueueTest::test_push()
+{
+	IntrusiveQueue<testContainer *> q1;
+
+	// size should be 0 initially
+	ut_compare("size initially 0", q1.size(), 0);
+	ut_assert_true(q1.empty());
+
+	// insert 100
+	for (int i = 0; i < 100; i++) {
+		testContainer *t = new testContainer();
+		t->i = i;
+
+		ut_compare("size increasing with i", q1.size(), i);
+		q1.push(t);
+		ut_compare("size increasing with i", q1.size(), i + 1);
+
+		ut_assert_true(!q1.empty());
+	}
+
+	// verify full size (100)
+	ut_compare("size 100", q1.size(), 100);
+
+	// pop all elements
+	for (int i = 0; i < 100; i++) {
+		auto node = q1.front();
+		q1.pop();
+		delete node;
+	}
+
+	// verify list has been cleared
+	ut_compare("size 0", q1.size(), 0);
+	ut_assert_true(q1.empty());
+
+	return true;
+}
+
+bool IntrusiveQueueTest::test_pop()
+{
+	IntrusiveQueue<testContainer *> q1;
+
+	// size should be 0 initially
+	ut_compare("size initially 0", q1.size(), 0);
+	ut_assert_true(q1.empty());
+
+	// insert 100
+	for (int i = 0; i < 100; i++) {
+		testContainer *t = new testContainer();
+		t->i = i;
+		q1.push(t);
+	}
+
+	// verify full size (100)
+	ut_assert_true(q1.size() == 100);
+
+	for (int i = 0; i < 100; i++) {
+		auto node = q1.front();
+		ut_compare("stored i", i, node->i);
+
+		ut_compare("size check", q1.size(), 100 - i);
+		q1.pop();
+		ut_compare("size check", q1.size(), 100 - i - 1);
+
+		delete node;
+
+		ut_compare("size check", q1.size(), 100 - i - 1);
+	}
+
+	// verify list has been cleared
+	ut_assert_true(q1.empty());
+	ut_compare("size check", q1.size(), 0);
+
+	// pop an empty queue
+	auto T = q1.pop();
+	ut_assert_true(T == nullptr);
+	ut_assert_true(q1.empty());
+	ut_compare("size check", q1.size(), 0);
+
+	return true;
+}
+
+bool IntrusiveQueueTest::test_push_duplicate()
+{
+	IntrusiveQueue<testContainer *> q1;
+
+	// size should be 0 initially
+	ut_compare("size initially 0", q1.size(), 0);
+	ut_assert_true(q1.empty());
+
+	// insert 100
+	for (int i = 0; i < 100; i++) {
+		testContainer *t = new testContainer();
+		t->i = i;
+
+		ut_compare("size increasing with i", q1.size(), i);
+		q1.push(t);
+		ut_compare("size increasing with i", q1.size(), i + 1);
+
+		ut_assert_true(!q1.empty());
+	}
+
+	// verify full size (100)
+	ut_compare("size 100", q1.size(), 100);
+
+
+	// attempt to insert front again
+	const auto q1_front = q1.front();
+	const auto q1_front_i = q1_front->i; // copy i value
+
+	const auto q1_back = q1.back();
+	const auto q1_back_i = q1_back->i; // copy i value
+
+	// push front and back aagain
+	q1.push(q1_front);
+	q1.push(q1_back);
+
+	// verify no change
+	ut_compare("size 100", q1.size(), 100);
+	ut_compare("q front not reinserted", q1.front()->i, q1_front->i);
+	ut_compare("q back not reinserted", q1.back()->i, q1_back->i);
+
+
+	// pop the head
+	const auto q1_head = q1.pop();
+
+	// verfify size should now be 99
+	ut_compare("size 99", q1.size(), 99);
+
+	// push back on
+	q1.push(q1_head);
+
+	// verify size now 100 again
+	ut_compare("size 100", q1.size(), 100);
+
+
+	// pop all elements
+	for (int i = 0; i < 100; i++) {
+		auto node = q1.front();
+		q1.pop();
+		delete node;
+	}
+
+	// verify list has been cleared
+	ut_compare("size 0", q1.size(), 0);
+	ut_assert_true(q1.empty());
+
+	return true;
+}
+
+ut_declare_test_c(test_IntrusiveQueue, IntrusiveQueueTest)
diff --git a/src/systemcmds/tests/tests_main.c b/src/systemcmds/tests/tests_main.c
index b1dfd86c80..14cf01836b 100644
--- a/src/systemcmds/tests/tests_main.c
+++ b/src/systemcmds/tests/tests_main.c
@@ -95,6 +95,7 @@ const struct {
 	{"hrt",			test_hrt,		OPT_NOJIGTEST | OPT_NOALLTEST},
 	{"hysteresis",		test_hysteresis,	0},
 	{"int",			test_int,		0},
+	{"IntrusiveQueue",	test_IntrusiveQueue,	0},
 	{"jig_voltages",	test_jig_voltages,	OPT_NOALLTEST},
 	{"List",		test_List,		0},
 	{"mathlib",		test_mathlib,		0},
diff --git a/src/systemcmds/tests/tests_main.h b/src/systemcmds/tests/tests_main.h
index 5d9d0fc3e8..40743cc9e4 100644
--- a/src/systemcmds/tests/tests_main.h
+++ b/src/systemcmds/tests/tests_main.h
@@ -57,6 +57,7 @@ extern int test_hott_telemetry(int argc, char *argv[]);
 extern int test_hrt(int argc, char *argv[]);
 extern int test_hysteresis(int argc, char *argv[]);
 extern int test_int(int argc, char *argv[]);
+extern int test_IntrusiveQueue(int argc, char *argv[]);
 extern int test_jig_voltages(int argc, char *argv[]);
 extern int test_led(int argc, char *argv[]);
 extern int test_List(int argc, char *argv[]);
-- 
GitLab