Make an ErrorList based on SizedArray
Change-Id: I6e37d1b6831daf902c625be81b982b3cb57e00c9
Signed-off-by: Ravago Jones <ravagojones@gmail.com>
diff --git a/aos/containers/BUILD b/aos/containers/BUILD
index 9fdc93f..ee1bf98 100644
--- a/aos/containers/BUILD
+++ b/aos/containers/BUILD
@@ -64,6 +64,30 @@
)
cc_library(
+ name = "error_list",
+ hdrs = [
+ "error_list.h",
+ ],
+ deps = [
+ ":sized_array",
+ "//aos:flatbuffers",
+ ],
+)
+
+cc_test(
+ name = "error_list_test",
+ srcs = [
+ "error_list_test.cc",
+ ],
+ target_compatible_with = ["@platforms//os:linux"],
+ deps = [
+ ":error_list",
+ "//aos:json_to_flatbuffer_fbs",
+ "//aos/testing:googletest",
+ ],
+)
+
+cc_library(
name = "resizeable_buffer",
hdrs = [
"resizeable_buffer.h",
diff --git a/aos/containers/error_list.h b/aos/containers/error_list.h
new file mode 100644
index 0000000..2fbd39e
--- /dev/null
+++ b/aos/containers/error_list.h
@@ -0,0 +1,130 @@
+#ifndef AOS_CONTAINERS_ERROR_LIST_H_
+#define AOS_CONTAINERS_ERROR_LIST_H_
+
+#include <iostream>
+
+#include "aos/containers/sized_array.h"
+#include "flatbuffers/flatbuffers.h"
+
+namespace aos {
+
+// A de-duplicated sorted array based on SizedArray
+// For keeping a list of errors that a subsystem has thrown
+// to publish them in a Status message.
+// It is designed to use flatbuffer enums, and use the reserved fields MAX and
+// MIN to automatically determine how much capacity it needs to have.
+template <typename T>
+class ErrorList {
+ private:
+ using array = SizedArray<T, static_cast<size_t>(T::MAX) -
+ static_cast<size_t>(T::MIN) + 1>;
+ array array_;
+
+ public:
+ using value_type = typename array::value_type;
+ using size_type = typename array::size_type;
+ using difference_type = typename array::difference_type;
+ using reference = typename array::reference;
+ using const_reference = typename array::const_reference;
+ using pointer = typename array::pointer;
+ using const_pointer = typename array::const_pointer;
+ using iterator = typename array::iterator;
+ using const_iterator = typename array::const_iterator;
+ using reverse_iterator = typename array::reverse_iterator;
+ using const_reverse_iterator = typename array::const_reverse_iterator;
+
+ constexpr ErrorList() = default;
+ ErrorList(const ErrorList &) = default;
+ ErrorList(ErrorList &&) = default;
+ ErrorList(const flatbuffers::Vector<T> &array) : array_() {
+ for (auto it = array.begin(); it < array.end(); it++) {
+ array_.push_back(*it);
+ }
+ std::sort(array_.begin(), array_.end());
+ };
+
+ ErrorList &operator=(const ErrorList &) = default;
+ ErrorList &operator=(ErrorList &&) = default;
+
+ bool operator==(const ErrorList &other) const {
+ if (other.size() != size()) {
+ return false;
+ }
+ for (size_t i = 0; i < size(); ++i) {
+ if (other[i] != (*this)[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+ bool operator!=(const ErrorList &other) const { return !(*this == other); }
+
+ reference at(size_t i) { return array_.at(i); }
+ const_reference at(size_t i) const { return array_.at(i); }
+
+ reference operator[](size_t i) { return array_[i]; }
+ const_reference operator[](size_t i) const { return array_[i]; }
+
+ reference front() { return array_.front(); }
+ const_reference front() const { return array_.front(); }
+
+ reference back() { return array_.back(); }
+ const_reference back() const { return array_.back(); }
+
+ T *data() { return array_.data(); }
+ const T *data() const { return array_.data(); }
+
+ iterator begin() { return array_.begin(); }
+ const_iterator begin() const { return array_.begin(); }
+ const_iterator cbegin() const { return array_.cbegin(); }
+
+ iterator end() { return array_.end(); }
+ const_iterator end() const { return array_.end(); }
+ const_iterator cend() const { return array_.cend(); }
+
+ reverse_iterator rbegin() { return array_.rbegin(); }
+ const_reverse_iterator rbegin() const { return array_.rbegin(); }
+ const_reverse_iterator crbegin() const { return array_.crbegin(); }
+
+ reverse_iterator rend() { return array_.rend(); }
+ const_reverse_iterator rend() const { return array_.rend(); }
+ const_reverse_iterator crend() const { return array_.crend(); }
+
+ bool empty() const { return array_.empty(); }
+ bool full() const { return array_.full(); }
+
+ size_t size() const { return array_.size(); }
+ constexpr size_t max_size() const { return array_.max_size(); }
+
+ void Clear(const T t) {
+ iterator index = std::find(array_.begin(), array_.end(), t);
+ if (index != array_.end()) {
+ array_.erase(index);
+ }
+ }
+
+ void Set(const T t) {
+ iterator position = std::lower_bound(array_.begin(), array_.end(), t);
+
+ // if it found something, and that something is the same, just leave it
+ if (position != array_.end() && *position == t) {
+ return;
+ }
+
+ // key doesn't already exist
+ array_.insert(position, t);
+ }
+
+ bool Has(const T t) {
+ return std::binary_search(array_.begin(), array_.end(), t);
+ }
+
+ flatbuffers::Offset<flatbuffers::Vector<T>> ToFlatbuffer(
+ flatbuffers::FlatBufferBuilder *fbb) const {
+ return fbb->CreateVector(array_.data(), array_.size());
+ }
+}; // namespace aos
+
+} // namespace aos
+
+#endif // AOS_CONTAINERS_ERROR_LIST_H_
diff --git a/aos/containers/error_list_test.cc b/aos/containers/error_list_test.cc
new file mode 100644
index 0000000..3ce23c4
--- /dev/null
+++ b/aos/containers/error_list_test.cc
@@ -0,0 +1,115 @@
+#include "aos/containers/error_list.h"
+
+#include "aos/json_to_flatbuffer_generated.h"
+#include "gtest/gtest.h"
+
+namespace aos {
+namespace testing {
+
+enum class TestEnum : int8_t {
+ FOO = 0,
+ BAR = 1,
+ BAZ = 2,
+ VWEEP = 3,
+ MIN = FOO,
+ MAX = VWEEP
+};
+
+// Tests that setting works and allows no duplicates
+TEST(ErrorListTest, NoDuplicates) {
+ ErrorList<TestEnum> a;
+ EXPECT_EQ(a.size(), 0);
+ a.Set(TestEnum::BAZ);
+ EXPECT_EQ(a.at(0), TestEnum::BAZ);
+ EXPECT_EQ(a.size(), 1);
+ a.Set(TestEnum::BAZ);
+ EXPECT_EQ(a.at(0), TestEnum::BAZ);
+ EXPECT_EQ(a.size(), 1);
+ a.Set(TestEnum::VWEEP);
+ EXPECT_EQ(a.at(0), TestEnum::BAZ);
+ EXPECT_EQ(a.at(1), TestEnum::VWEEP);
+ EXPECT_EQ(a.size(), 2);
+ a.Set(TestEnum::FOO);
+ EXPECT_EQ(a.at(0), TestEnum::FOO);
+ EXPECT_EQ(a.at(1), TestEnum::BAZ);
+ EXPECT_EQ(a.at(2), TestEnum::VWEEP);
+ EXPECT_EQ(a.size(), 3);
+}
+
+// Tests that clearing works
+TEST(ErrorListTest, Clearing) {
+ ErrorList<TestEnum> a;
+ a.Set(TestEnum::FOO);
+ a.Set(TestEnum::BAZ);
+ a.Set(TestEnum::VWEEP);
+ EXPECT_EQ(a.at(0), TestEnum::FOO);
+ EXPECT_EQ(a.at(1), TestEnum::BAZ);
+ EXPECT_EQ(a.at(2), TestEnum::VWEEP);
+ EXPECT_EQ(a.size(), 3);
+
+ a.Clear(TestEnum::BAR);
+ EXPECT_EQ(a.at(0), TestEnum::FOO);
+ EXPECT_EQ(a.at(1), TestEnum::BAZ);
+ EXPECT_EQ(a.at(2), TestEnum::VWEEP);
+ EXPECT_EQ(a.size(), 3);
+
+ a.Clear(TestEnum::BAZ);
+ EXPECT_EQ(a.at(0), TestEnum::FOO);
+ EXPECT_EQ(a.at(1), TestEnum::VWEEP);
+ EXPECT_EQ(a.size(), 2);
+}
+
+// Tests that checking for a value works
+TEST(ErrorListTest, Has) {
+ ErrorList<TestEnum> a;
+ a.Set(TestEnum::FOO);
+ a.Set(TestEnum::BAZ);
+ a.Set(TestEnum::VWEEP);
+ EXPECT_EQ(a.at(0), TestEnum::FOO);
+ EXPECT_EQ(a.at(1), TestEnum::BAZ);
+ EXPECT_EQ(a.at(2), TestEnum::VWEEP);
+ EXPECT_EQ(a.size(), 3);
+
+ EXPECT_TRUE(a.Has(TestEnum::FOO));
+ EXPECT_TRUE(a.Has(TestEnum::VWEEP));
+ EXPECT_TRUE(a.Has(TestEnum::BAZ));
+ EXPECT_FALSE(a.Has(TestEnum::BAR));
+}
+
+// Tests serializing and deserializing to/from flatbuffers.
+TEST(ErrorListTest, Flatbuffers) {
+ ErrorList<BaseType> a;
+ a.Set(BaseType::Bool);
+ a.Set(BaseType::Float);
+ a.Set(BaseType::Short);
+ EXPECT_TRUE(a.Has(BaseType::Bool));
+ EXPECT_TRUE(a.Has(BaseType::Short));
+ EXPECT_TRUE(a.Has(BaseType::Float));
+ EXPECT_EQ(a.at(0), BaseType::Bool);
+ EXPECT_EQ(a.at(1), BaseType::Short);
+ EXPECT_EQ(a.at(2), BaseType::Float);
+ EXPECT_EQ(a.size(), 3);
+
+ flatbuffers::FlatBufferBuilder fbb(1024);
+ flatbuffers::Offset<flatbuffers::Vector<BaseType>> vector =
+ a.ToFlatbuffer(&fbb);
+
+ ConfigurationBuilder builder(fbb);
+ builder.add_vector_foo_enum(vector);
+
+ fbb.Finish(builder.Finish());
+ const Configuration *config =
+ flatbuffers::GetRoot<Configuration>(fbb.GetBufferPointer());
+
+ ErrorList<BaseType> b(*config->vector_foo_enum());
+ EXPECT_TRUE(b.Has(BaseType::Bool));
+ EXPECT_TRUE(b.Has(BaseType::Short));
+ EXPECT_TRUE(b.Has(BaseType::Float));
+ EXPECT_EQ(b.at(0), BaseType::Bool);
+ EXPECT_EQ(b.at(1), BaseType::Short);
+ EXPECT_EQ(b.at(2), BaseType::Float);
+ EXPECT_EQ(b.size(), 3);
+}
+
+} // namespace testing
+} // namespace aos
diff --git a/aos/containers/sized_array_test.cc b/aos/containers/sized_array_test.cc
index ae732bc..d055f40 100644
--- a/aos/containers/sized_array_test.cc
+++ b/aos/containers/sized_array_test.cc
@@ -175,5 +175,70 @@
EXPECT_DEATH(a.emplace_back(5), "Aborted at");
}
+// Tests inserting at various positions in the array.
+TEST(SizedArrayTest, Inserting) {
+ SizedArray<int, 5> a;
+ a.insert(a.begin(), 2);
+ EXPECT_EQ(a.at(0), 2);
+ EXPECT_EQ(a.size(), 1);
+
+ a.emplace_back(3);
+ EXPECT_EQ(a.at(0), 2);
+ EXPECT_EQ(a.at(1), 3);
+ EXPECT_EQ(a.size(), 2);
+
+ a.insert(a.begin(), 0);
+ EXPECT_EQ(a.at(0), 0);
+ EXPECT_EQ(a.at(1), 2);
+ EXPECT_EQ(a.at(2), 3);
+ EXPECT_EQ(a.size(), 3);
+
+ a.insert(a.begin() + 1, 1);
+ EXPECT_EQ(a.at(0), 0);
+ EXPECT_EQ(a.at(1), 1);
+ EXPECT_EQ(a.at(2), 2);
+ EXPECT_EQ(a.at(3), 3);
+ EXPECT_EQ(a.size(), 4);
+
+ a.insert(a.begin() + 1, 0);
+ EXPECT_EQ(a.at(0), 0);
+ EXPECT_EQ(a.at(1), 0);
+ EXPECT_EQ(a.at(2), 1);
+ EXPECT_EQ(a.at(3), 2);
+ EXPECT_EQ(a.at(4), 3);
+ EXPECT_EQ(a.size(), 5);
+}
+
+// Tests erasing things from the array
+TEST(SizedArrayTest, Erasing) {
+ SizedArray<int, 5> a;
+ a.push_back(8);
+ a.push_back(9);
+ a.push_back(7);
+ a.push_back(1);
+ a.push_back(5);
+ EXPECT_EQ(a.at(0), 8);
+ EXPECT_EQ(a.at(1), 9);
+ EXPECT_EQ(a.at(2), 7);
+ EXPECT_EQ(a.at(3), 1);
+ EXPECT_EQ(a.at(4), 5);
+ EXPECT_EQ(a.size(), 5);
+
+ a.erase(a.begin() + 1, a.begin() + 3);
+ EXPECT_EQ(a.at(0), 8);
+ EXPECT_EQ(a.at(1), 1);
+ EXPECT_EQ(a.at(2), 5);
+ EXPECT_EQ(a.size(), 3);
+
+ a.erase(a.begin());
+ EXPECT_EQ(a.at(0), 1);
+ EXPECT_EQ(a.at(1), 5);
+ EXPECT_EQ(a.size(), 2);
+
+ a.erase(a.end() - 1);
+ EXPECT_EQ(a.at(0), 1);
+ EXPECT_EQ(a.size(), 1);
+}
+
} // namespace testing
} // namespace aos