From 84bd115ebda5f281831379def906c706ad47f4c5 Mon Sep 17 00:00:00 2001 From: Green Sky Date: Thu, 18 Mar 2021 20:05:58 +0100 Subject: [PATCH] add a scalar range helper class --- framework/std_utils/CMakeLists.txt | 7 +- framework/std_utils/src/mm/scalar_range2.hpp | 61 ++++ framework/std_utils/test/CMakeLists.txt | 15 + .../std_utils/test/scalar_range2_test.cpp | 267 ++++++++++++++++++ 4 files changed, 346 insertions(+), 4 deletions(-) create mode 100644 framework/std_utils/src/mm/scalar_range2.hpp create mode 100644 framework/std_utils/test/CMakeLists.txt create mode 100644 framework/std_utils/test/scalar_range2_test.cpp diff --git a/framework/std_utils/CMakeLists.txt b/framework/std_utils/CMakeLists.txt index 7ac129e..06fd6fd 100644 --- a/framework/std_utils/CMakeLists.txt +++ b/framework/std_utils/CMakeLists.txt @@ -5,9 +5,8 @@ add_library(std_utils INTERFACE) target_include_directories(std_utils INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/src") -# TODO: test -#if (BUILD_TESTING) - #add_subdirectory(test) -#endif() +if (BUILD_TESTING) + add_subdirectory(test) +endif() diff --git a/framework/std_utils/src/mm/scalar_range2.hpp b/framework/std_utils/src/mm/scalar_range2.hpp new file mode 100644 index 0000000..45ab7a8 --- /dev/null +++ b/framework/std_utils/src/mm/scalar_range2.hpp @@ -0,0 +1,61 @@ +#pragma once + +namespace MM { + +template +struct ScalarRange2 { + T v_min {}; + T v_max {}; + + ScalarRange2(void) = default; + + ScalarRange2(const T& min, const T& max) noexcept { + if (min <= max) { + v_min = min; + v_max = max; + } else { + v_min = max; + v_max = min; + } + } + + bool operator==(const ScalarRange2& rhs) const { + return min() == rhs.min() && max() == rhs.max(); + } + + bool operator!=(const ScalarRange2& rhs) const { + return !(*this == rhs); + } + + [[nodiscard]] T& min(void) { return v_min; } + [[nodiscard]] T& max(void) { return v_max; } + + [[nodiscard]] const T& min(void) const { return v_min; } + [[nodiscard]] const T& max(void) const { return v_max; } + + void setMin(const T& new_min) { + min() = new_min; + max() = max() < new_min ? new_min : max(); + } + + void setMax(const T& new_max) { + max() = new_max; + min() = min() > new_max ? new_max : min(); + } + + void sanitize(void) { + if (min() > max()) { + // TODO: is copy ok? + T tmp = min(); + min() = max(); + max() = tmp; + } + } + + [[nodiscard]] bool inRange(T&& value) const { + return value >= min() && value <= max(); + } +}; + +} // MM + diff --git a/framework/std_utils/test/CMakeLists.txt b/framework/std_utils/test/CMakeLists.txt new file mode 100644 index 0000000..b02a101 --- /dev/null +++ b/framework/std_utils/test/CMakeLists.txt @@ -0,0 +1,15 @@ +add_executable(std_utils_test + scalar_range2_test.cpp +) + +target_include_directories(std_utils_test PRIVATE ".") + +target_link_libraries(std_utils_test + gtest_main + gmock + + std_utils +) + +add_test(NAME std_utils_test COMMAND std_utils_test) + diff --git a/framework/std_utils/test/scalar_range2_test.cpp b/framework/std_utils/test/scalar_range2_test.cpp new file mode 100644 index 0000000..9a55d28 --- /dev/null +++ b/framework/std_utils/test/scalar_range2_test.cpp @@ -0,0 +1,267 @@ +#include + +#include + +#include + +TEST(std_utils_scalar_range2, signed_integer_good) { + MM::ScalarRange2 r8; + ASSERT_EQ(r8.min(), 0) << "default initialized"; + ASSERT_EQ(r8.max(), 0) << "default initialized"; + + MM::ScalarRange2 r16 {}; + ASSERT_EQ(r16.min(), 0) << "default initialized"; + ASSERT_EQ(r16.max(), 0) << "default initialized"; + + MM::ScalarRange2 r32 {1, 10}; + ASSERT_EQ(r32.min(), 1) << "ctr"; + ASSERT_EQ(r32.max(), 10) << "ctr"; + + MM::ScalarRange2 r64{-100, 42}; + ASSERT_EQ(r64.min(), -100) << "ctr"; + ASSERT_EQ(r64.max(), 42) << "ctr"; + + ASSERT_TRUE(r64.inRange(0)); + ASSERT_TRUE(r64.inRange(-100)); + ASSERT_TRUE(r64.inRange(42)); + ASSERT_FALSE(r64.inRange(-101)); + ASSERT_FALSE(r64.inRange(-100001)); + ASSERT_FALSE(r64.inRange(100001)); + ASSERT_FALSE(r64.inRange(43)); + + r64.sanitize(); + ASSERT_EQ(r64.min(), -100) << "sanitize"; + ASSERT_EQ(r64.max(), 42) << "sanitize"; + + MM::ScalarRange2 r64_2{r64.min(), r64.max()}; + ASSERT_EQ(r64.min(), r64_2.min()); + ASSERT_EQ(r64.max(), r64_2.max()); + MM::ScalarRange2 r64_3{0, 100}; // different range + + + // eq + ASSERT_EQ(r64, r64_2); + ASSERT_NE(r64, r64_3); + + // cpy + MM::ScalarRange2 r64_cpy_ctr{r64}; + ASSERT_EQ(r64_cpy_ctr, r64_2); + ASSERT_NE(r64_cpy_ctr, r64_3); + + MM::ScalarRange2 r64_cpy_ass{}; + r64_cpy_ass = r64; + ASSERT_EQ(r64_cpy_ass, r64_2); + ASSERT_NE(r64_cpy_ass, r64_3); +} + +TEST(std_utils_scalar_range2, signed_integer_evil) { + { + MM::ScalarRange2 r32 {100, -10}; // min > max in ctr + ASSERT_EQ(r32.min(), -10); + ASSERT_EQ(r32.max(), 100); + } + + { + MM::ScalarRange2 r32 {100, 100}; // min == max + ASSERT_EQ(r32.min(), 100); + ASSERT_EQ(r32.max(), 100); + } + + { + MM::ScalarRange2 r32 {100, 100}; // min == max + ASSERT_EQ(r32.min(), 100); + ASSERT_EQ(r32.max(), 100); + + r32.v_max = 20; // max < min, manual edit + ASSERT_EQ(r32.min(), 100); + ASSERT_EQ(r32.max(), 20); + + ASSERT_FALSE(r32.inRange(100)); + ASSERT_FALSE(r32.inRange(-100)); + ASSERT_FALSE(r32.inRange(20)); + ASSERT_FALSE(r32.inRange(0)); + + r32.sanitize(); + + ASSERT_EQ(r32.min(), 20); + ASSERT_EQ(r32.max(), 100); + } +} + +TEST(std_utils_scalar_range2, unsigned_integer_good) { + MM::ScalarRange2 r8; + ASSERT_EQ(r8.min(), 0) << "default initialized"; + ASSERT_EQ(r8.max(), 0) << "default initialized"; + + MM::ScalarRange2 r16 {}; + ASSERT_EQ(r16.min(), 0) << "default initialized"; + ASSERT_EQ(r16.max(), 0) << "default initialized"; + + MM::ScalarRange2 r32 {1, 10}; + ASSERT_EQ(r32.min(), 1) << "ctr"; + ASSERT_EQ(r32.max(), 10) << "ctr"; + + MM::ScalarRange2 r64{1, 42}; + ASSERT_EQ(r64.min(), 1) << "ctr"; + ASSERT_EQ(r64.max(), 42) << "ctr"; + + ASSERT_TRUE(r64.inRange(1)); + ASSERT_TRUE(r64.inRange(10)); + ASSERT_TRUE(r64.inRange(42)); + ASSERT_FALSE(r64.inRange(0)); + ASSERT_FALSE(r64.inRange(101)); + ASSERT_FALSE(r64.inRange(100001)); + ASSERT_FALSE(r64.inRange(100001)); + ASSERT_FALSE(r64.inRange(43)); + + r64.sanitize(); + ASSERT_EQ(r64.min(), 1) << "sanitize"; + ASSERT_EQ(r64.max(), 42) << "sanitize"; + + MM::ScalarRange2 r64_2{r64.min(), r64.max()}; + ASSERT_EQ(r64.min(), r64_2.min()); + ASSERT_EQ(r64.max(), r64_2.max()); + MM::ScalarRange2 r64_3{0, 100}; // different range + + + // eq + ASSERT_EQ(r64, r64_2); + ASSERT_NE(r64, r64_3); + + // cpy + MM::ScalarRange2 r64_cpy_ctr{r64}; + ASSERT_EQ(r64_cpy_ctr, r64_2); + ASSERT_NE(r64_cpy_ctr, r64_3); + + MM::ScalarRange2 r64_cpy_ass{}; + r64_cpy_ass = r64; + ASSERT_EQ(r64_cpy_ass, r64_2); + ASSERT_NE(r64_cpy_ass, r64_3); +} + +TEST(std_utils_scalar_range2, unsigned_integer_evil) { + { + MM::ScalarRange2 r32 {100, 10}; // min > max in ctr + ASSERT_EQ(r32.min(), 10); + ASSERT_EQ(r32.max(), 100); + } + + { + MM::ScalarRange2 r32 {100, 100}; // min == max + ASSERT_EQ(r32.min(), 100); + ASSERT_EQ(r32.max(), 100); + } + + { + MM::ScalarRange2 r32 {100, 100}; // min == max + ASSERT_EQ(r32.min(), 100); + ASSERT_EQ(r32.max(), 100); + + r32.v_max = 20; // max < min, manual edit + ASSERT_EQ(r32.min(), 100); + ASSERT_EQ(r32.max(), 20); + + ASSERT_FALSE(r32.inRange(100)); + ASSERT_FALSE(r32.inRange(20)); + ASSERT_FALSE(r32.inRange(0)); + + r32.sanitize(); + + ASSERT_EQ(r32.min(), 20); + ASSERT_EQ(r32.max(), 100); + } +} + +TEST(std_utils_scalar_range2, floating_good) { + { + MM::ScalarRange2 r32; + ASSERT_EQ(r32.min(), 0.f) << "default initialized"; + ASSERT_EQ(r32.max(), 0.f) << "default initialized"; + } + + { + MM::ScalarRange2 r32 {}; + ASSERT_EQ(r32.min(), 0.f) << "default initialized"; + ASSERT_EQ(r32.max(), 0.f) << "default initialized"; + } + + { + MM::ScalarRange2 r32 {1.f, 10.f}; + ASSERT_EQ(r32.min(), 1.f) << "ctr"; + ASSERT_EQ(r32.max(), 10.f) << "ctr"; + } + + MM::ScalarRange2 r64{1., 42.}; + ASSERT_EQ(r64.min(), 1.) << "ctr"; + ASSERT_EQ(r64.max(), 42.) << "ctr"; + + ASSERT_TRUE(r64.inRange(1.)); + ASSERT_TRUE(r64.inRange(1.1)); + ASSERT_TRUE(r64.inRange(1.3)); + ASSERT_TRUE(r64.inRange(10.)); + ASSERT_TRUE(r64.inRange(42.)); + ASSERT_FALSE(r64.inRange(0.)); + ASSERT_FALSE(r64.inRange(101.)); + ASSERT_FALSE(r64.inRange(100001.)); + ASSERT_FALSE(r64.inRange(100001.)); + ASSERT_FALSE(r64.inRange(43.)); + + r64.sanitize(); + ASSERT_EQ(r64.min(), 1.) << "sanitize"; + ASSERT_EQ(r64.max(), 42.) << "sanitize"; + + MM::ScalarRange2 r64_2{r64.min(), r64.max()}; + ASSERT_EQ(r64.min(), r64_2.min()); + ASSERT_EQ(r64.max(), r64_2.max()); + MM::ScalarRange2 r64_3{0., 100.}; // different range + + + // eq + ASSERT_EQ(r64, r64_2); + ASSERT_NE(r64, r64_3); + + // cpy + MM::ScalarRange2 r64_cpy_ctr{r64}; + ASSERT_EQ(r64_cpy_ctr, r64_2); + ASSERT_NE(r64_cpy_ctr, r64_3); + + MM::ScalarRange2 r64_cpy_ass{}; + r64_cpy_ass = r64; + ASSERT_EQ(r64_cpy_ass, r64_2); + ASSERT_NE(r64_cpy_ass, r64_3); +} + +TEST(std_utils_scalar_range2, floating_evil) { + { + MM::ScalarRange2 r32 {100.f, -10.f}; // min > max in ctr + ASSERT_EQ(r32.min(), -10.f); + ASSERT_EQ(r32.max(), 100.f); + } + + { + MM::ScalarRange2 r32 {100.f, 100.f}; // min == max + ASSERT_EQ(r32.min(), 100.f); + ASSERT_EQ(r32.max(), 100.f); + } + + { + MM::ScalarRange2 r32 {100.f, 100.f}; // min == max + ASSERT_EQ(r32.min(), 100.f); + ASSERT_EQ(r32.max(), 100.f); + + r32.v_max = 20.f; // max < min, manual edit + ASSERT_EQ(r32.min(), 100.f); + ASSERT_EQ(r32.max(), 20.f); + + ASSERT_FALSE(r32.inRange(100.f)); + ASSERT_FALSE(r32.inRange(-100.f)); + ASSERT_FALSE(r32.inRange(20.f)); + ASSERT_FALSE(r32.inRange(0.f)); + + r32.sanitize(); + + ASSERT_EQ(r32.min(), 20.f); + ASSERT_EQ(r32.max(), 100.f); + } +} +