From 86f859e0d9fc5def36b98b3b389e3fa9759bffc2 Mon Sep 17 00:00:00 2001 From: Tamas Petz Date: Thu, 14 Dec 2023 17:54:01 +0100 Subject: [PATCH 01/14] [test] Add --seed option, and a default seed A global randomized seed value should be used everywhere to initialize pseudorandom number generators. A seed can alternatively be specified through command line using '--seed '. This feature will help in reproducing failures in the future. --- test/framework/test_main.cpp | 17 +++++++++++++++++ test/framework/utils.h | 8 ++++++++ 2 files changed, 25 insertions(+) diff --git a/test/framework/test_main.cpp b/test/framework/test_main.cpp index e30a8268b..2c88f42a2 100644 --- a/test/framework/test_main.cpp +++ b/test/framework/test_main.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include "framework/utils.h" @@ -13,16 +14,20 @@ namespace test { size_t Options::vector_length_{16}; +uint64_t Options::seed_{0}; /// Parses command line arguments. static void parse_arguments(int argc, char **argv) { // clang-format off static struct option long_options[] = { {"vector-length", required_argument, nullptr, 'v'}, + {"seed", required_argument, nullptr, 's'}, {0, 0, nullptr, 0} }; // clang-format on + bool is_seed_set = false; + for (;;) { int option_index = 0; int opt = getopt_long(argc, argv, "", long_options, &option_index); @@ -38,11 +43,23 @@ static void parse_arguments(int argc, char **argv) { case 'v': Options::set_vector_length(std::stoi(optarg)); break; + + case 's': + Options::set_seed(std::stoull(optarg)); + is_seed_set = true; + break; } } + // Set a random seed if it is not explicitly specified. + if (!is_seed_set) { + std::random_device rd; + Options::set_seed(static_cast(rd())); + } + std::cout << "Vector length is set to " << Options::vector_length() << " bytes." << std::endl; + std::cout << "Seed is set to " << Options::seed() << "." << std::endl; } } // namespace test diff --git a/test/framework/utils.h b/test/framework/utils.h index 2fbc47019..e4a0621a6 100644 --- a/test/framework/utils.h +++ b/test/framework/utils.h @@ -24,6 +24,9 @@ class Options { /// Returns the vector length being tested. This is in bytes. static size_t vector_length() { return vector_length_; } + /// Returns seed to use. + static uint64_t seed() { return seed_; } + /// Returns the number of lanes in a vector for a given integral type. template , bool> = true> @@ -43,9 +46,14 @@ class Options { vector_length_ = value; } + /// Sets the seed. + static void set_seed(uint64_t value) { seed_ = value; } + private: /// Vector length being tested. static size_t vector_length_; + /// Seed to use. + static uint64_t seed_; }; // end of class Options } // namespace test -- GitLab From f348f452eed04e230c30ce83ae139d84e2ca5af3 Mon Sep 17 00:00:00 2001 From: Tamas Petz Date: Thu, 14 Dec 2023 18:02:00 +0100 Subject: [PATCH 02/14] [test] Introduce TwoDimensional This abstract base class serves as an interface for objects which represent a 2D array. A helper 'dump()' is capable of printing the elements of such an object. --- test/CMakeLists.txt | 5 +++- test/api/CMakeLists.txt | 4 +-- test/framework/CMakeLists.txt | 4 ++- test/framework/abstract.h | 38 ++++++++++++++++++++++++++ test/framework/utils.cpp | 50 +++++++++++++++++++++++++++++++++++ test/framework/utils.h | 6 +++++ 6 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 test/framework/abstract.h create mode 100644 test/framework/utils.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 053f6385a..ef0dca87a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -14,7 +14,10 @@ set(INTRINSICCV_WARNING_FLAGS "-Wold-style-cast" ) -set(INTRINSICCV_TEST_MAIN ${CMAKE_CURRENT_SOURCE_DIR}/framework/test_main.cpp) +set(INTRINSICCV_TEST_FRAMEWORK_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/framework/test_main.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/framework/utils.cpp +) include(FetchContent) FetchContent_Declare( diff --git a/test/api/CMakeLists.txt b/test/api/CMakeLists.txt index b76740d8c..5cc3a47b9 100644 --- a/test/api/CMakeLists.txt +++ b/test/api/CMakeLists.txt @@ -2,9 +2,9 @@ # # SPDX-License-Identifier: Apache-2.0 -file(GLOB intrinsiccv_api_test_sources CONFIGURE_DEPENDS "*.h" "*.cpp") +file(GLOB intrinsiccv_api_test_sources CONFIGURE_DEPENDS "*.h" "test_*.cpp") -list(APPEND intrinsiccv_api_test_sources ${INTRINSICCV_TEST_MAIN}) +list(APPEND intrinsiccv_api_test_sources ${INTRINSICCV_TEST_FRAMEWORK_SOURCES}) set_source_files_properties( ${intrinsiccv_api_test_sources} diff --git a/test/framework/CMakeLists.txt b/test/framework/CMakeLists.txt index 1c5c9eb9d..1a0633031 100644 --- a/test/framework/CMakeLists.txt +++ b/test/framework/CMakeLists.txt @@ -2,7 +2,9 @@ # # SPDX-License-Identifier: Apache-2.0 -file(GLOB intrinsiccv_framework_test_sources CONFIGURE_DEPENDS "*.h" "*.cpp") +file(GLOB intrinsiccv_framework_test_sources CONFIGURE_DEPENDS "*.h" "test_*.cpp") + +list(APPEND intrinsiccv_framework_test_sources ${INTRINSICCV_TEST_FRAMEWORK_SOURCES}) set_source_files_properties( ${intrinsiccv_framework_test_sources} diff --git a/test/framework/abstract.h b/test/framework/abstract.h new file mode 100644 index 000000000..3ed7c9683 --- /dev/null +++ b/test/framework/abstract.h @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2023 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef INTRINSICCV_TEST_FRAMEWORK_ABSTRACT_H_ +#define INTRINSICCV_TEST_FRAMEWORK_ABSTRACT_H_ + +#include + +namespace test { + +/// Interface for objects which represent a finite two-dimensional array. +template +class TwoDimensional { + public: + virtual ~TwoDimensional() = default; + + /// Returns the number of elements in a row of a two-dimensional array. + virtual size_t width() const = 0; + + /// Returns the number of rows of a two-dimensional array. + virtual size_t height() const = 0; + + /// Returns the number of channels. + virtual size_t channels() const { return 1ul; } + + /// Returns a pointer to a data element at a given row and column position, or + /// nullptr if the requested position is invalid. + virtual ElementType *at(size_t row, size_t column) = 0; + + /// Returns a const pointer to a data element at a given row and column + /// position, or nullptr if the requested position is invalid. + virtual const ElementType *at(size_t row, size_t column) const = 0; +}; // end of class TwoDimensional + +} // namespace test + +#endif // INTRINSICCV_TEST_FRAMEWORK_ABSTRACT_H_ diff --git a/test/framework/utils.cpp b/test/framework/utils.cpp new file mode 100644 index 000000000..889ee3fff --- /dev/null +++ b/test/framework/utils.cpp @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2023 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#include "framework/utils.h" + +#include + +#include +#include +#include +#include +#include +#include + +namespace test { + +template +void dump(const TwoDimensional *elements) { + if (!elements) { + return; + } + + auto mask = std::numeric_limits>::max(); + + for (size_t row = 0; row < elements->height(); ++row) { + for (size_t column = 0; column < elements->width(); ++column) { + ElementType value = elements->at(row, column)[0]; + std::cout << std::setw(2 * sizeof(ElementType)) << std::setfill('0') + << std::hex << (static_cast(value) & mask) << " "; + } + + std::cout << std::endl; + } + + if ((elements->height() > 0) && (elements->width() > 0)) { + std::cout << std::endl << std::flush; + } +} + +template void dump(const TwoDimensional *); +template void dump(const TwoDimensional *); +template void dump(const TwoDimensional *); +template void dump(const TwoDimensional *); +template void dump(const TwoDimensional *); +template void dump(const TwoDimensional *); +template void dump(const TwoDimensional *); +template void dump(const TwoDimensional *); + +} // namespace test diff --git a/test/framework/utils.h b/test/framework/utils.h index e4a0621a6..b91926134 100644 --- a/test/framework/utils.h +++ b/test/framework/utils.h @@ -10,6 +10,8 @@ #include #include +#include "framework/abstract.h" + #define INTRINSICCV_API(name, impl, type) \ template , bool> = true> \ @@ -56,6 +58,10 @@ class Options { static uint64_t seed_; }; // end of class Options +/// Prints all the elements in a two-dimensional space. +template +void dump(const TwoDimensional *elements); + } // namespace test #endif // INTRINSICCV_TEST_FRAMEWORK_UTILS_H_ -- GitLab From ff307c405a7f6ce3bcf4b4324ff67796dd7789b8 Mon Sep 17 00:00:00 2001 From: Tamas Petz Date: Thu, 14 Dec 2023 18:25:15 +0100 Subject: [PATCH 03/14] [test] Implement TwoDimensional for Array2D --- test/framework/array.h | 32 ++++++++++++++++++++------- test/framework/test_array2d.cpp | 39 +++++++++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 10 deletions(-) diff --git a/test/framework/array.h b/test/framework/array.h index ff5ce77d0..c53fe5355 100644 --- a/test/framework/array.h +++ b/test/framework/array.h @@ -16,20 +16,26 @@ #include #include +#include "framework/abstract.h" + namespace test { /// A simple two-dimensional array representation. template -class Array2D final { +class Array2D final : public TwoDimensional { public: Array2D() = default; explicit Array2D(size_t width, size_t height) : Array2D(width, height, 0) {} - explicit Array2D(size_t width, size_t height, size_t padding_bytes) + explicit Array2D(size_t width, size_t height, size_t padding) + : Array2D(width, height, padding, 1) {} + + explicit Array2D(size_t width, size_t height, size_t padding, size_t channels) : width_{width}, height_{height}, - stride_{width * sizeof(ElementType) + padding_bytes} { + channels_{channels}, + stride_{width * sizeof(ElementType) + padding} { try { data_ = std::make_unique(height_ * stride_); } catch (...) { @@ -57,8 +63,9 @@ class Array2D final { data_ = std::move(other.data_); width_ = other.width_; height_ = other.height_; + channels_ = other.channels_; stride_ = other.stride_; - other.width_ = other.height_ = other.stride_ = 0; + other.width_ = other.height_ = other.channels_ = other.stride_ = 0; return *this; } @@ -116,10 +123,13 @@ class Array2D final { } /// Returns the width of this array. - size_t width() const { return width_; } + size_t width() const override { return width_; } /// Returns the height of this array. - size_t height() const { return height_; } + size_t height() const override { return height_; } + + /// Returns the number of channels. + size_t channels() const override { return channels_; }; /// Returns the stride of this array. size_t stride() const { return stride_; } @@ -129,14 +139,14 @@ class Array2D final { /// Returns a pointer to a data element at a given row and column position, or /// nullptr if the requested position is invalid. - ElementType *at(size_t row, size_t column) { + ElementType *at(size_t row, size_t column) override { return const_cast( const_cast *>(this)->at(row, column)); } /// Returns a constant pointer to a data element at a given row and column /// position, or nullptr if the requested position is invalid. - const ElementType *at(size_t row, size_t column) const { + const ElementType *at(size_t row, size_t column) const override { if (!check_access(row, column)) { return nullptr; } @@ -216,6 +226,8 @@ class Array2D final { size_t width_{0}; /// Number of rows in the array. size_t height_{0}; + /// Number of channels. + size_t channels_{0}; /// Stride in bytes between the first elements of two consecutive rows. size_t stride_{0}; }; // end of class Array2D @@ -227,6 +239,8 @@ class Array2D final { << "Mismatch in width." << std::endl; \ ASSERT_EQ((__lhs).height(), (__rhs).height()) \ << "Mismatch in height." << std::endl; \ + ASSERT_EQ((__lhs).channels(), (__rhs).channels()) \ + << "Mismatch in channels." << std::endl; \ auto mismatch = (__lhs).compare_to((__rhs)); \ if (mismatch) { \ auto [row, col] = *mismatch; \ @@ -243,6 +257,8 @@ class Array2D final { << "Mismatch in width." << std::endl; \ ASSERT_EQ((__lhs).height(), (__rhs).height()) \ << "Mismatch in height." << std::endl; \ + ASSERT_EQ((__lhs).channels(), (__rhs).channels()) \ + << "Mismatch in channels." << std::endl; \ auto mismatch = (__lhs).compare_to((__rhs)); \ if (!mismatch) { \ FAIL() << "Objects are equal, but expected to differ." << std::endl; \ diff --git a/test/framework/test_array2d.cpp b/test/framework/test_array2d.cpp index cb606319f..a2e1541d0 100644 --- a/test/framework/test_array2d.cpp +++ b/test/framework/test_array2d.cpp @@ -16,6 +16,7 @@ TEST(Array2D, DefaultConstructor) { test::Array2D array; EXPECT_EQ(array.width(), 0); EXPECT_EQ(array.height(), 0); + EXPECT_EQ(array.channels(), 0); EXPECT_EQ(array.stride(), 0); EXPECT_FALSE(array.valid()); } @@ -27,6 +28,7 @@ TEST(Array2D, Constructor) { test::Array2D array_1{width, height}; EXPECT_EQ(array_1.width(), width); EXPECT_EQ(array_1.height(), height); + EXPECT_EQ(array_1.channels(), 1); EXPECT_EQ(array_1.stride(), width * sizeof(uint32_t)); EXPECT_TRUE(array_1.valid()); @@ -36,17 +38,20 @@ TEST(Array2D, Constructor) { /// Tests that the move assignment operator of test::Array2D works. TEST(Array2D, MoveAssignment) { - size_t width = 5, height = 5; - test::Array2D array_1{width, height}; + size_t width = 5, height = 5, channels = 3; + test::Array2D array_1{width, height, 0, channels}; test::Array2D array_2; array_2 = std::move(array_1); EXPECT_EQ(array_2.width(), width); EXPECT_EQ(array_2.height(), height); + EXPECT_EQ(array_2.channels(), channels); EXPECT_EQ(array_2.stride(), width * sizeof(uint32_t)); EXPECT_TRUE(array_2.valid()); + EXPECT_EQ(array_1.width(), 0); EXPECT_EQ(array_1.height(), 0); + EXPECT_EQ(array_1.channels(), 0); EXPECT_EQ(array_1.stride(), 0); EXPECT_FALSE(array_1.valid()); } @@ -162,6 +167,21 @@ TEST(Array2D, ExpectEq_NotEqual_Height) { EXPECT_FATAL_FAILURE(Test::test(), "Mismatch in height."); } +/// Tests that EXPECT_EQ_ARRAY2D() macro works for non-equal objects where +/// channels is different. +TEST(Array2D, ExpectEq_NotEqual_Channels) { + struct Test { + static void test() { + size_t width = 5, height = 2; + test::Array2D array_1{width, height, 0, 1}; + test::Array2D array_2{width, height, 0, 2}; + EXPECT_EQ_ARRAY2D(array_1, array_2); + } + }; + + EXPECT_FATAL_FAILURE(Test::test(), "Mismatch in channels."); +} + /// Tests that EXPECT_EQ_ARRAY2D() macro works for non-equal objects where data /// is different. TEST(Array2D, ExpectEq_NotEqual_Data) { @@ -223,6 +243,21 @@ TEST(Array2D, ExpectNe_NotEqual_Height) { EXPECT_FATAL_FAILURE(Test::test(), "Mismatch in height."); } +/// Tests that EXPECT_NE_ARRAY2D() macro works for non-equal objects where +/// channels is different. +TEST(Array2D, ExpectNe_NotEqual_Channels) { + struct Test { + static void test() { + size_t width = 5, height = 2; + test::Array2D array_1{width, height, 0, 1}; + test::Array2D array_2{width, height, 0, 2}; + EXPECT_NE_ARRAY2D(array_1, array_2); + } + }; + + EXPECT_FATAL_FAILURE(Test::test(), "Mismatch in channels."); +} + /// Tests that EXPECT_NE_ARRAY2D() macro works for non-equal objects where there /// is a difference in data. TEST(Array2D, ExpectNe_NotEqual_Data) { -- GitLab From 018e324a83885857908db3cbf593167bfe176eff Mon Sep 17 00:00:00 2001 From: Tamas Petz Date: Fri, 15 Dec 2023 11:18:39 +0100 Subject: [PATCH 04/14] [test] Improve Array2D This change aims to slightly improve the implementation. --- test/framework/array.h | 117 +++++++++++++++++++------------- test/framework/test_array2d.cpp | 43 ++++++------ test/framework/utils.h | 9 +++ 3 files changed, 101 insertions(+), 68 deletions(-) diff --git a/test/framework/array.h b/test/framework/array.h index c53fe5355..816bb2245 100644 --- a/test/framework/array.h +++ b/test/framework/array.h @@ -17,6 +17,7 @@ #include #include "framework/abstract.h" +#include "framework/utils.h" namespace test { @@ -36,13 +37,7 @@ class Array2D final : public TwoDimensional { height_{height}, channels_{channels}, stride_{width * sizeof(ElementType) + padding} { - try { - data_ = std::make_unique(height_ * stride_); - } catch (...) { - width_ = height_ = stride_ = 0; - return; - } - + try_allocate(); fill_padding(); } @@ -53,7 +48,7 @@ class Array2D final : public TwoDimensional { Array2D(const Array2D &other) = delete; /// Move constructor. - Array2D(Array2D &&other) = delete; + Array2D(Array2D &&other) { this->operator=(other); } /// Copy assignment operator. Array2D &operator=(const Array2D &other) = delete; @@ -65,13 +60,15 @@ class Array2D final : public TwoDimensional { height_ = other.height_; channels_ = other.channels_; stride_ = other.stride_; - other.width_ = other.height_ = other.channels_ = other.stride_ = 0; + other.reset(); return *this; } /// Fills the underlying memory range with a given value skipping padding /// bytes. void fill(ElementType value) { + ASSERT_EQ(valid(), true); + ElementType *ptr = data(); for (size_t row = 0; row < height(); ++row) { for (size_t column = 0; column < width(); ++column) { @@ -82,10 +79,12 @@ class Array2D final : public TwoDimensional { } } - /// Sets values starting in a given row starting at a given column. Returns - /// false if there is not enough space in the row. + /// Sets values in a row starting at a given column. void set(size_t row, size_t column, std::initializer_list values) { + ASSERT_EQ(valid(), true) << "Array is invalid."; + ASSERT_GE(width() - column, values.size()); + ElementType *ptr = at(row, column); if (!ptr) { return; @@ -105,7 +104,7 @@ class Array2D final : public TwoDimensional { for (size_t column = 0; column < width(); ++column) { const ElementType *lhs = at(row, column); const ElementType *rhs = other.at(row, column); - if (!lhs || !rhs || lhs[0] != rhs[0]) { + if (!lhs || !rhs || (lhs[0] != rhs[0])) { return std::make_tuple(row, column); } } @@ -134,7 +133,7 @@ class Array2D final : public TwoDimensional { /// Returns the stride of this array. size_t stride() const { return stride_; } - /// Returns true if this object hold actual memory, otherwise false. + /// Returns true if this object holds actual memory, otherwise false. bool valid() const { return data() != nullptr; } /// Returns a pointer to a data element at a given row and column position, or @@ -144,11 +143,12 @@ class Array2D final : public TwoDimensional { const_cast *>(this)->at(row, column)); } - /// Returns a constant pointer to a data element at a given row and column + /// Returns a const pointer to a data element at a given row and column /// position, or nullptr if the requested position is invalid. const ElementType *at(size_t row, size_t column) const override { if (!check_access(row, column)) { - return nullptr; + TEST_FAIL_WITH(nullptr, + "Access is either out-of-bounds or the array is invalid."); } const ElementType *ptr = add_stride(data(), row); @@ -156,7 +156,7 @@ class Array2D final : public TwoDimensional { } private: - /// Returns the offset to the padding within a row. + /// Returns the offset to the first padding byte within a row. size_t padding_offset() const { return width() * sizeof(ElementType); } /// Returns true if a row has padding, otherwise false. @@ -183,7 +183,7 @@ class Array2D final : public TwoDimensional { } } - /// Checks for clobbered padding, if present. + /// Checks for clobbered padding bytes, if present. void check_padding() const { if (!valid() || !has_padding()) { return; @@ -193,9 +193,8 @@ class Array2D final : public TwoDimensional { for (size_t row = 0; row < height(); ++row) { for (size_t offset = padding_offset(); offset < stride(); ++offset) { if (ptr[offset] != kPaddingValue) { - FAIL() << "Padding byte was overwritten at (row=" << row - << ", offset=" << offset << ")" << std::endl; - return; + GTEST_FAIL() << "Padding byte was overwritten at (row=" << row + << ", offset=" << offset << ")" << std::endl; } } @@ -205,18 +204,37 @@ class Array2D final : public TwoDimensional { /// Adds stride to a pointer. ElementType *add_stride(ElementType *ptr, size_t count) { - size_t address = reinterpret_cast(ptr); + char *address = reinterpret_cast(ptr); address += count * stride(); return reinterpret_cast(address); } /// Adds stride to a pointer. const ElementType *add_stride(const ElementType *ptr, size_t count) const { - size_t address = reinterpret_cast(ptr); + const char *address = reinterpret_cast(ptr); address += count * stride(); return reinterpret_cast(address); } + /// Resets the instance to the default instance. + void reset() { + width_ = height_ = channels_ = stride_ = 0; + data_.reset(); + } + + /// Tries to allocate backing memory. + void try_allocate() { + size_t allocation_size = height_ * stride_; + + try { + data_ = std::make_unique(allocation_size); + } catch (...) { + reset(); + GTEST_FAIL() << "Failed to allocate memory of " << allocation_size + << " bytes"; + } + } + /// Constant value of row padding bytes. static constexpr uint8_t kPaddingValue = std::numeric_limits::max(); @@ -233,36 +251,37 @@ class Array2D final : public TwoDimensional { }; // end of class Array2D /// Compares two \ref Array2D objects for equality. -#define EXPECT_EQ_ARRAY2D(__lhs, __rhs) \ - do { \ - ASSERT_EQ((__lhs).width(), (__rhs).width()) \ - << "Mismatch in width." << std::endl; \ - ASSERT_EQ((__lhs).height(), (__rhs).height()) \ - << "Mismatch in height." << std::endl; \ - ASSERT_EQ((__lhs).channels(), (__rhs).channels()) \ - << "Mismatch in channels." << std::endl; \ - auto mismatch = (__lhs).compare_to((__rhs)); \ - if (mismatch) { \ - auto [row, col] = *mismatch; \ - FAIL() << "Mismatch at (row=" << row << ", col=" << col \ - << "): " << (__lhs).at(row, col)[0] << " vs " \ - << (__rhs).at(row, col)[0] << "." << std::endl; \ - } \ +#define EXPECT_EQ_ARRAY2D(lhs, rhs) \ + do { \ + ASSERT_EQ((lhs).width(), (rhs).width()) \ + << "Mismatch in width." << std::endl; \ + ASSERT_EQ((lhs).height(), (rhs).height()) \ + << "Mismatch in height." << std::endl; \ + ASSERT_EQ((lhs).channels(), (rhs).channels()) \ + << "Mismatch in channels." << std::endl; \ + auto mismatch = (lhs).compare_to((rhs)); \ + if (mismatch) { \ + auto [row, col] = *mismatch; \ + GTEST_FAIL() << "Mismatch at (row=" << row << ", col=" << col \ + << "): " << (lhs).at(row, col)[0] << " vs " \ + << (rhs).at(row, col)[0] << "." << std::endl; \ + } \ } while (0 != 0) /// Compares two \ref Array2D objects for inequality. -#define EXPECT_NE_ARRAY2D(__lhs, __rhs) \ - do { \ - ASSERT_EQ((__lhs).width(), (__rhs).width()) \ - << "Mismatch in width." << std::endl; \ - ASSERT_EQ((__lhs).height(), (__rhs).height()) \ - << "Mismatch in height." << std::endl; \ - ASSERT_EQ((__lhs).channels(), (__rhs).channels()) \ - << "Mismatch in channels." << std::endl; \ - auto mismatch = (__lhs).compare_to((__rhs)); \ - if (!mismatch) { \ - FAIL() << "Objects are equal, but expected to differ." << std::endl; \ - } \ +#define EXPECT_NE_ARRAY2D(lhs, rhs) \ + do { \ + ASSERT_EQ((lhs).width(), (rhs).width()) \ + << "Mismatch in width." << std::endl; \ + ASSERT_EQ((lhs).height(), (rhs).height()) \ + << "Mismatch in height." << std::endl; \ + ASSERT_EQ((lhs).channels(), (rhs).channels()) \ + << "Mismatch in channels." << std::endl; \ + auto mismatch = (lhs).compare_to((rhs)); \ + if (!mismatch) { \ + GTEST_FAIL() << "Objects are equal, but expected to differ." \ + << std::endl; \ + } \ } while (0 != 0) } // namespace test diff --git a/test/framework/test_array2d.cpp b/test/framework/test_array2d.cpp index a2e1541d0..1d1cd99dc 100644 --- a/test/framework/test_array2d.cpp +++ b/test/framework/test_array2d.cpp @@ -59,25 +59,16 @@ TEST(Array2D, MoveAssignment) { /// Tests that test::Array2D.at() works for set/get. TEST(Array2D, At) { size_t width = 1, height = 1; - test::Array2D array_1{width, height}; - test::Array2D array_2{width, height}; - - array_1.at(0, 0)[0] = 1; - EXPECT_EQ(array_1.at(0, 0)[0], 1); + test::Array2D array{width, height}; - array_1.at(0, 0)[0] = 2; - EXPECT_EQ(array_1.at(0, 0)[0], 2); + array.at(0, 0)[0] = 1; + EXPECT_EQ(array.at(0, 0)[0], 1); - EXPECT_EQ(array_1.at(0, 1), nullptr); - EXPECT_EQ(array_1.at(1, 0), nullptr); - EXPECT_EQ(array_1.at(1, 1), nullptr); + array.at(0, 0)[0] = 2; + EXPECT_EQ(array.at(0, 0)[0], 2); - // Constant array const test::Array2D const_array{width, height}; - - EXPECT_EQ(const_array.at(0, 1), nullptr); - EXPECT_EQ(const_array.at(0, 1), nullptr); - EXPECT_EQ(const_array.at(0, 1), nullptr); + EXPECT_EQ(const_array.at(0, 0)[0], 0); } /// Tests that test::Array2D.set() works. @@ -303,13 +294,27 @@ TEST(Array2D, PaddingClobbered_LastRow_LastByte) { "Padding byte was overwritten at (row=1, offset=29)"); } -/// Additional tests for coverage purposes. -TEST(Array2D, Coverage) { +static void Array2DCoverageBadAlloc() { test::Array2D array{std::numeric_limits::max(), 1}; EXPECT_FALSE(array.valid()); +} +static void Array2DCoverageSetInvalidArray() { + test::Array2D array; array.set(0, 0, {}); +} + +static void Array2DCoverageAtInvalidArray() { + test::Array2D array; + array.at(0, 0); +} - uint64_t* ptr_1 = array.at(0, 0); - EXPECT_EQ(ptr_1, nullptr); +/// Additional tests for coverage purposes. +TEST(Array2D, Coverage) { + EXPECT_FATAL_FAILURE(Array2DCoverageBadAlloc(), + "Failed to allocate memory of"); + EXPECT_FATAL_FAILURE(Array2DCoverageSetInvalidArray(), "Array is invalid."); + EXPECT_FATAL_FAILURE( + Array2DCoverageAtInvalidArray(), + "Access is either out-of-bounds or the array is invalid."); } diff --git a/test/framework/utils.h b/test/framework/utils.h index b91926134..fa7c35d22 100644 --- a/test/framework/utils.h +++ b/test/framework/utils.h @@ -19,6 +19,15 @@ return &impl; \ } +// Generates a fatal failure with a generic message, and returns with a given +// value. +#define TEST_FAIL_WITH(return_value, message) \ + do { \ + GTEST_MESSAGE_("Failed", ::testing::TestPartResult::kFatalFailure) \ + << message; \ + return (return_value); \ + } while (0 != 0) + namespace test { class Options { -- GitLab From c1f1ac4ab44db40beeadd1e92888c3e8e24d4a79 Mon Sep 17 00:00:00 2001 From: Tamas Petz Date: Fri, 15 Dec 2023 16:39:23 +0100 Subject: [PATCH 05/14] [test] Add copy assignment operator to Array2D --- test/framework/array.h | 18 ++++++++++++++++-- test/framework/test_array2d.cpp | 10 ++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/test/framework/array.h b/test/framework/array.h index 816bb2245..16d16a4c3 100644 --- a/test/framework/array.h +++ b/test/framework/array.h @@ -45,13 +45,27 @@ class Array2D final : public TwoDimensional { ~Array2D() { check_padding(); } /// Copy constructor. - Array2D(const Array2D &other) = delete; + Array2D(const Array2D &other) { this->operator=(other); } /// Move constructor. Array2D(Array2D &&other) { this->operator=(other); } /// Copy assignment operator. - Array2D &operator=(const Array2D &other) = delete; + Array2D &operator=(const Array2D &other) { + width_ = other.width_; + height_ = other.height_; + channels_ = other.channels_; + stride_ = other.stride_; + + try_allocate(); + + EXPECT_TRUE(valid()); + if (valid()) { + std::memcpy(data_.get(), other.data_.get(), height_ * stride_); + } + + return *this; + } /// Move assignment operator. Array2D &operator=(Array2D &&other) { diff --git a/test/framework/test_array2d.cpp b/test/framework/test_array2d.cpp index 1d1cd99dc..5f1e51207 100644 --- a/test/framework/test_array2d.cpp +++ b/test/framework/test_array2d.cpp @@ -36,6 +36,16 @@ TEST(Array2D, Constructor) { EXPECT_EQ_ARRAY2D(array_1, array_2); } +/// Tests that the copy assignment operator of test::Array2D works. +TEST(Array2D, CopyAssignment) { + size_t width = 5, height = 5; + test::Array2D array_1{width, height}; + + test::Array2D array_2 = array_1; + EXPECT_EQ_ARRAY2D(array_1, array_2); + EXPECT_NE(array_1.data(), array_2.data()); +} + /// Tests that the move assignment operator of test::Array2D works. TEST(Array2D, MoveAssignment) { size_t width = 5, height = 5, channels = 3; -- GitLab From 5a3e35e591ee65a08b7bd94fa52861f6fc3f0353 Mon Sep 17 00:00:00 2001 From: Tamas Petz Date: Fri, 15 Dec 2023 12:49:16 +0100 Subject: [PATCH 06/14] [test] Add support for bordering Only replicate border type is supported by prepare_borders(). --- test/CMakeLists.txt | 1 + test/framework/abstract.h | 18 +++++ test/framework/border.cpp | 83 ++++++++++++++++++++ test/framework/border.h | 22 ++++++ test/framework/test_border.cpp | 139 +++++++++++++++++++++++++++++++++ 5 files changed, 263 insertions(+) create mode 100644 test/framework/border.cpp create mode 100644 test/framework/border.h create mode 100644 test/framework/test_border.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ef0dca87a..b60d2dba6 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -15,6 +15,7 @@ set(INTRINSICCV_WARNING_FLAGS ) set(INTRINSICCV_TEST_FRAMEWORK_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/framework/border.cpp ${CMAKE_CURRENT_SOURCE_DIR}/framework/test_main.cpp ${CMAKE_CURRENT_SOURCE_DIR}/framework/utils.cpp ) diff --git a/test/framework/abstract.h b/test/framework/abstract.h index 3ed7c9683..a9cf4aece 100644 --- a/test/framework/abstract.h +++ b/test/framework/abstract.h @@ -33,6 +33,24 @@ class TwoDimensional { virtual const ElementType *at(size_t row, size_t column) const = 0; }; // end of class TwoDimensional +/// Interface for objects which have a border. +class Bordered { + public: + virtual ~Bordered() = default; + + /// Returns left border width. + virtual size_t left() const = 0; + + /// Returns top border height. + virtual size_t top() const = 0; + + /// Returns right border width. + virtual size_t right() const = 0; + + /// Returns bottom border height. + virtual size_t bottom() const = 0; +}; // end of class Bordered + } // namespace test #endif // INTRINSICCV_TEST_FRAMEWORK_ABSTRACT_H_ diff --git a/test/framework/border.cpp b/test/framework/border.cpp new file mode 100644 index 000000000..a30e1737f --- /dev/null +++ b/test/framework/border.cpp @@ -0,0 +1,83 @@ +// SPDX-FileCopyrightText: 2023 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#include "framework/border.h" + +#include + +#include + +namespace test { + +/// Creates replicated border elements. +/// +/// Replicating means that the elements at the edges are copied to bordering +/// element positions. For example: +/// | left border | elements | right border | +/// | A A | A B C D E | E E | +template +static void replicate(const Bordered *bordered, + TwoDimensional *elements) { + ASSERT_LE((bordered->left() + bordered->right()) * elements->channels(), + elements->width()); + ASSERT_LE(bordered->top() + bordered->bottom(), elements->height()); + + // Replicate left and right border columns. + for (size_t row = 0; row < elements->height(); ++row) { + for (size_t channel = 0; channel < elements->channels(); ++channel) { + // Prepare left border columns. + for (size_t column = 0; column < bordered->left(); ++column) { + size_t src_column = bordered->left() * elements->channels() + channel; + size_t dst_column = column * elements->channels() + channel; + elements->at(row, dst_column)[0] = elements->at(row, src_column)[0]; + } + + // Prepare right border columns. + for (size_t column = 0; column < bordered->right(); ++column) { + size_t src_column = elements->width() - + (bordered->right() + 1) * elements->channels() + + channel; + size_t dst_column = src_column + (1 + column) * elements->channels(); + elements->at(row, dst_column)[0] = elements->at(row, src_column)[0]; + } + } + } + + // Replicate top border rows. + size_t replicated_top_row = bordered->top(); + for (size_t row = 0; row < bordered->top(); ++row) { + auto row_ptr = elements->at(replicated_top_row, 0); + std::copy(row_ptr, row_ptr + elements->width(), elements->at(row, 0)); + } + + // Replicate bottom border rows. + size_t replicated_bottom_row = elements->height() - bordered->bottom() - 1; + for (size_t row = 0; row < bordered->bottom(); ++row) { + auto row_ptr = elements->at(replicated_bottom_row, 0); + std::copy(row_ptr, row_ptr + elements->width(), + elements->at(replicated_bottom_row + row + 1, 0)); + } +} + +template +void prepare_borders(intrinsiccv_border_type_t border_type, + const Bordered *bordered, + TwoDimensional *elements) { + ASSERT_NE(bordered, nullptr); + ASSERT_NE(elements, nullptr); + + switch (border_type) { + default: + GTEST_FAIL() << "Border type is not implemented."; + + case INTRINSICCV_BORDER_TYPE_REPLICATE: + return replicate(bordered, elements); + } +} + +template void prepare_borders(intrinsiccv_border_type_t, + const Bordered *, + TwoDimensional *); + +} // namespace test diff --git a/test/framework/border.h b/test/framework/border.h new file mode 100644 index 000000000..4ed31ab58 --- /dev/null +++ b/test/framework/border.h @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2023 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef INTRINSICCV_TEST_FRAMEWORK_BORDER_H_ +#define INTRINSICCV_TEST_FRAMEWORK_BORDER_H_ + +#include + +#include "framework/abstract.h" + +namespace test { + +/// Prepares bordering elements given a border type and bordering requirements. +template +void prepare_borders(intrinsiccv_border_type_t border_type, + const Bordered *bordered, + TwoDimensional *elements); + +} // namespace test + +#endif // INTRINSICCV_TEST_FRAMEWORK_BORDER_H_ diff --git a/test/framework/test_border.cpp b/test/framework/test_border.cpp new file mode 100644 index 000000000..ebbf03fc1 --- /dev/null +++ b/test/framework/test_border.cpp @@ -0,0 +1,139 @@ +// SPDX-FileCopyrightText: 2023 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include "framework/abstract.h" +#include "framework/array.h" +#include "framework/border.h" + +class ImplementsBorder : public test::Bordered { + public: + explicit ImplementsBorder(size_t left, size_t top, size_t right, + size_t bottom) + : left_{left}, top_{top}, right_{right}, bottom_{bottom} {} + + size_t left() const override { return left_; }; + size_t top() const override { return top_; }; + size_t right() const override { return right_; }; + size_t bottom() const override { return bottom_; }; + + private: + size_t left_; + size_t top_; + size_t right_; + size_t bottom_; +}; // end of class ImplementsBorder + +/// Tests that replicate border strategy works for one channel and one bordering +/// element in every direction. +TEST(Border, Replicate_1Ch_1Element) { + using ElementType = uint8_t; + + const size_t width = 6, height = 5; + + test::Array2D actual{width, height}; + actual.set(0, 0, {0, 0, 0, 0, 0, 0}); + actual.set(1, 0, {0, 1, 2, 3, 4, 0}); + actual.set(2, 0, {0, 5, 6, 7, 8, 0}); + actual.set(3, 0, {0, 9, 0, 1, 2, 0}); + actual.set(4, 0, {0, 0, 0, 0, 0, 0}); + + test::Array2D expected{width, height}; + expected.set(0, 0, {1, 1, 2, 3, 4, 4}); + expected.set(1, 0, {1, 1, 2, 3, 4, 4}); + expected.set(2, 0, {5, 5, 6, 7, 8, 8}); + expected.set(3, 0, {9, 9, 0, 1, 2, 2}); + expected.set(4, 0, {9, 9, 0, 1, 2, 2}); + + ImplementsBorder bordered{1, 1, 1, 1}; + test::prepare_borders(INTRINSICCV_BORDER_TYPE_REPLICATE, + &bordered, &actual); + EXPECT_EQ_ARRAY2D(expected, actual); +} + +/// Tests that replicate border strategy works for one channel and two bordering +/// elements in every direction. +TEST(Border, Replicate_1Ch_2Elements) { + using ElementType = uint8_t; + + const size_t width = 6, height = 6; + + test::Array2D actual{width, height}; + actual.set(0, 0, {0, 0, 0, 0, 0, 0}); + actual.set(1, 0, {0, 0, 0, 0, 0, 0}); + actual.set(2, 0, {0, 0, 1, 2, 0, 0}); + actual.set(3, 0, {0, 0, 3, 4, 0, 0}); + actual.set(4, 0, {0, 0, 0, 0, 0, 0}); + actual.set(5, 0, {0, 0, 0, 0, 0, 0}); + + test::Array2D expected{width, height}; + expected.set(0, 0, {1, 1, 1, 2, 2, 2}); + expected.set(1, 0, {1, 1, 1, 2, 2, 2}); + expected.set(2, 0, {1, 1, 1, 2, 2, 2}); + expected.set(3, 0, {3, 3, 3, 4, 4, 4}); + expected.set(4, 0, {3, 3, 3, 4, 4, 4}); + expected.set(5, 0, {3, 3, 3, 4, 4, 4}); + + ImplementsBorder bordered{2, 2, 2, 2}; + test::prepare_borders(INTRINSICCV_BORDER_TYPE_REPLICATE, + &bordered, &actual); + EXPECT_EQ_ARRAY2D(expected, actual); +} + +/// Tests that replicate border strategy works for two channels and one +/// bordering element in every direction. +TEST(Border, Replicate_2Ch_1Element) { + using ElementType = uint8_t; + + const size_t width = 8, height = 5, channels = 2; + + test::Array2D actual{width, height, 0, channels}; + actual.set(0, 0, {0, 0, 0, 0, 0, 0, 0, 0}); + actual.set(1, 0, {0, 0, 1, 2, 3, 4, 0, 0}); + actual.set(2, 0, {0, 0, 5, 6, 7, 8, 0, 0}); + actual.set(3, 0, {0, 0, 9, 1, 2, 3, 0, 0}); + actual.set(4, 0, {0, 0, 0, 0, 0, 0, 0, 0}); + + test::Array2D expected{width, height, 0, channels}; + expected.set(0, 0, {1, 2, 1, 2, 3, 4, 3, 4}); + expected.set(1, 0, {1, 2, 1, 2, 3, 4, 3, 4}); + expected.set(2, 0, {5, 6, 5, 6, 7, 8, 7, 8}); + expected.set(3, 0, {9, 1, 9, 1, 2, 3, 2, 3}); + expected.set(4, 0, {9, 1, 9, 1, 2, 3, 2, 3}); + + ImplementsBorder bordered{1, 1, 1, 1}; + test::prepare_borders(INTRINSICCV_BORDER_TYPE_REPLICATE, + &bordered, &actual); + EXPECT_EQ_ARRAY2D(expected, actual); +} + +/// Tests that replicate border strategy works for two channels and two +/// bordering elements in every direction. +TEST(Border, Replicate_2Ch_2Elements) { + using ElementType = uint8_t; + + const size_t width = 12, height = 6, channels = 2; + + test::Array2D actual{width, height, 0, channels}; + actual.set(0, 0, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); + actual.set(1, 0, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); + actual.set(2, 0, {0, 0, 0, 0, 1, 2, 3, 4, 0, 0, 0, 0}); + actual.set(3, 0, {0, 0, 0, 0, 5, 6, 7, 8, 0, 0, 0, 0}); + actual.set(4, 0, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); + actual.set(5, 0, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); + + test::Array2D expected{width, height, 0, channels}; + expected.set(0, 0, {1, 2, 1, 2, 1, 2, 3, 4, 3, 4, 3, 4}); + expected.set(1, 0, {1, 2, 1, 2, 1, 2, 3, 4, 3, 4, 3, 4}); + expected.set(2, 0, {1, 2, 1, 2, 1, 2, 3, 4, 3, 4, 3, 4}); + expected.set(3, 0, {5, 6, 5, 6, 5, 6, 7, 8, 7, 8, 7, 8}); + expected.set(4, 0, {5, 6, 5, 6, 5, 6, 7, 8, 7, 8, 7, 8}); + expected.set(5, 0, {5, 6, 5, 6, 5, 6, 7, 8, 7, 8, 7, 8}); + + ImplementsBorder bordered{2, 2, 2, 2}; + test::prepare_borders(INTRINSICCV_BORDER_TYPE_REPLICATE, + &bordered, &actual); + EXPECT_EQ_ARRAY2D(expected, actual); +} -- GitLab From 1bd95a15267ef610b25dcdfe36f8e3198be91c68 Mon Sep 17 00:00:00 2001 From: Tamas Petz Date: Fri, 15 Dec 2023 13:00:18 +0100 Subject: [PATCH 07/14] [test] Add PseudoRandomNumberGenerator This generator generates pseudorandom numbers of a given type. When constructed, it uses the global seed value, which makes testing reproducible. --- test/framework/abstract.h | 14 ++++++++++++ test/framework/generator.h | 36 +++++++++++++++++++++++++++++++ test/framework/test_generator.cpp | 19 ++++++++++++++++ 3 files changed, 69 insertions(+) create mode 100644 test/framework/generator.h create mode 100644 test/framework/test_generator.cpp diff --git a/test/framework/abstract.h b/test/framework/abstract.h index a9cf4aece..15156e153 100644 --- a/test/framework/abstract.h +++ b/test/framework/abstract.h @@ -6,6 +6,7 @@ #define INTRINSICCV_TEST_FRAMEWORK_ABSTRACT_H_ #include +#include namespace test { @@ -51,6 +52,19 @@ class Bordered { virtual size_t bottom() const = 0; }; // end of class Bordered +/// Interface for objects which generate some values. +template +class Generator { + public: + virtual ~Generator() = default; + + /// Resets the generator to its initial state. + virtual void reset() {} + + /// Yields the next value or std::nullopt. + virtual std::optional next() = 0; +}; // end of class Generator + } // namespace test #endif // INTRINSICCV_TEST_FRAMEWORK_ABSTRACT_H_ diff --git a/test/framework/generator.h b/test/framework/generator.h new file mode 100644 index 000000000..b36c29233 --- /dev/null +++ b/test/framework/generator.h @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2023 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef INTRINSICCV_TEST_FRAMEWORK_GENERATOR_H_ +#define INTRINSICCV_TEST_FRAMEWORK_GENERATOR_H_ + +#include + +#include "framework/abstract.h" +#include "framework/utils.h" + +namespace test { + +/// Generates pseudo-random numbers of a given type. +template +class PseudoRandomNumberGenerator : public Generator { + public: + PseudoRandomNumberGenerator() : seed_{Options::seed()} { reset(); } + + /// Resets the generator to the initial state. + void reset() override { rng_.seed(seed_); } + + /// Yields the next value or std::nullopt. + std::optional next() override { + return static_cast(rng_()); + } + + protected: + uint64_t seed_; + std::mt19937_64 rng_; +}; // end of class PseudoRandomNumberGenerator + +} // namespace test + +#endif // INTRINSICCV_TEST_FRAMEWORK_GENERATOR_H_ diff --git a/test/framework/test_generator.cpp b/test/framework/test_generator.cpp new file mode 100644 index 000000000..6bcd8e86e --- /dev/null +++ b/test/framework/test_generator.cpp @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2023 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include "framework/generator.h" + +/// Tests test::PseudoRandomNumberGenerator::reset() works. +TEST(PseudoRandomNumberGenerator, Reset) { + using ElementType = uint8_t; + + test::PseudoRandomNumberGenerator generator; + ElementType initial_value = generator.next().value(); + generator.next(); + generator.reset(); + ElementType value = generator.next().value(); + EXPECT_EQ(initial_value, value); +} -- GitLab From f4b8aa9e35667133e28fcedab835e609b0dcba88 Mon Sep 17 00:00:00 2001 From: Tamas Petz Date: Fri, 15 Dec 2023 16:47:43 +0100 Subject: [PATCH 08/14] [test] Add Kernel Such an instance represents a kernel operator with one channel. --- test/framework/array.h | 2 +- test/framework/kernel.h | 54 ++++++++++++++++++++++++ test/framework/test_kernel.cpp | 76 ++++++++++++++++++++++++++++++++++ test/framework/types.h | 21 ++++++++++ 4 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 test/framework/kernel.h create mode 100644 test/framework/test_kernel.cpp create mode 100644 test/framework/types.h diff --git a/test/framework/array.h b/test/framework/array.h index 16d16a4c3..25a928523 100644 --- a/test/framework/array.h +++ b/test/framework/array.h @@ -23,7 +23,7 @@ namespace test { /// A simple two-dimensional array representation. template -class Array2D final : public TwoDimensional { +class Array2D : public TwoDimensional { public: Array2D() = default; diff --git a/test/framework/kernel.h b/test/framework/kernel.h new file mode 100644 index 000000000..a0d707b96 --- /dev/null +++ b/test/framework/kernel.h @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2023 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef INTRINSICCV_TEST_FRAMEWORK_KERNEL_H_ +#define INTRINSICCV_TEST_FRAMEWORK_KERNEL_H_ + +#include "framework/abstract.h" +#include "framework/array.h" +#include "framework/types.h" + +namespace test { + +/// Represents a kernel operator. +template +class Kernel : protected Array2D, public Bordered { + public: + // Make some features of Array2D base class public. + using Array2D::at; + using Array2D::channels; + using Array2D::height; + using Array2D::width; + + explicit Kernel(Array2D mask) + : Array2D(mask), + anchor_{mask.width() / 2, mask.height() / 2} {} + + /// Returns the anchor point of the kernel. + Point anchor() const { return anchor_; } + + /// Returns the number of elements to the left of the anchor point. + size_t left() const override { return anchor().x; } + + /// Returns the number of elements above the anchor point. + size_t top() const override { return anchor().y; } + + /// Returns the number of elements to the right of the anchor point. + size_t right() const override { + return (width() > 0) ? width() - anchor().x - 1 : 0; + } + + /// Returns the number of elements above the anchor point. + size_t bottom() const override { + return (height() > 0) ? height() - anchor().y - 1 : 0; + } + + private: + /// The anchor point of the kernel. + Point anchor_; +}; // end of class Kernel + +} // namespace test + +#endif // INTRINSICCV_TEST_FRAMEWORK_KERNEL_H_ diff --git a/test/framework/test_kernel.cpp b/test/framework/test_kernel.cpp new file mode 100644 index 000000000..e5426d4e5 --- /dev/null +++ b/test/framework/test_kernel.cpp @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2023 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include "framework/kernel.h" + +/// Tests that the constructor of test::Kernel works for odd width and +/// height. +TEST(Kernel, ConstructOdd) { + using ElementType = uint8_t; + + test::Array2D mask{3, 3}; + mask.set(0, 0, {1, 2, 3}); + mask.set(2, 0, {4, 5, 6}); + + test::Kernel kernel{mask}; + EXPECT_EQ(kernel.width(), 3); + EXPECT_EQ(kernel.height(), 3); + EXPECT_EQ(kernel.channels(), 1); + EXPECT_EQ(kernel.anchor().x, 1); + EXPECT_EQ(kernel.anchor().y, 1); + + EXPECT_EQ(kernel.top(), 1); + EXPECT_EQ(kernel.left(), 1); + EXPECT_EQ(kernel.bottom(), 1); + EXPECT_EQ(kernel.right(), 1); + + EXPECT_EQ(kernel.at(0, 0)[0], 1); + EXPECT_EQ(kernel.at(0, 1)[0], 2); + EXPECT_EQ(kernel.at(0, 2)[0], 3); + EXPECT_EQ(kernel.at(1, 0)[0], 0); + EXPECT_EQ(kernel.at(1, 1)[0], 0); + EXPECT_EQ(kernel.at(1, 2)[0], 0); + EXPECT_EQ(kernel.at(2, 0)[0], 4); + EXPECT_EQ(kernel.at(2, 1)[0], 5); + EXPECT_EQ(kernel.at(2, 2)[0], 6); +} + +/// Tests that the constructor of test::Kernel works for even width and +/// height. +TEST(Kernel, ConstructEven) { + using ElementType = uint8_t; + + test::Array2D mask{4, 4}; + test::Kernel kernel{mask}; + EXPECT_EQ(kernel.width(), 4); + EXPECT_EQ(kernel.height(), 4); + EXPECT_EQ(kernel.channels(), 1); + EXPECT_EQ(kernel.anchor().x, 2); + EXPECT_EQ(kernel.anchor().y, 2); + + EXPECT_EQ(kernel.top(), 2); + EXPECT_EQ(kernel.left(), 2); + EXPECT_EQ(kernel.bottom(), 1); + EXPECT_EQ(kernel.right(), 1); +} + +/// Tests that the default constructor of test::Kernel works with all +/// zero arguments. +TEST(Kernel, ConstructEmpty) { + using ElementType = uint8_t; + + test::Array2D mask{0, 0}; + test::Kernel kernel{mask}; + EXPECT_EQ(kernel.width(), 0); + EXPECT_EQ(kernel.height(), 0); + EXPECT_EQ(kernel.channels(), 1); + EXPECT_EQ(kernel.anchor().x, 0); + EXPECT_EQ(kernel.anchor().y, 0); + EXPECT_EQ(kernel.top(), 0); + EXPECT_EQ(kernel.left(), 0); + EXPECT_EQ(kernel.bottom(), 0); + EXPECT_EQ(kernel.right(), 0); +} diff --git a/test/framework/types.h b/test/framework/types.h new file mode 100644 index 000000000..93e423091 --- /dev/null +++ b/test/framework/types.h @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2023 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef INTRINSICCV_TEST_FRAMEWORK_TYPES_H_ +#define INTRINSICCV_TEST_FRAMEWORK_TYPES_H_ + +#include + +namespace test { + +/// Represents a point with two non-negative coordinates where 'x' is the +/// horizontal and 'y' is the vertical component. +struct Point { + size_t x; + size_t y; +}; // end of struct Point + +} // namespace test + +#endif // INTRINSICCV_TEST_FRAMEWORK_TYPES_H_ -- GitLab From c315d7114833f51bdee05b26fd980190362c21c6 Mon Sep 17 00:00:00 2001 From: Tamas Petz Date: Fri, 15 Dec 2023 17:11:36 +0100 Subject: [PATCH 09/14] [test] Add ArrayLayout and extend Array2D ArrayLayout describes the layout of something which is two-dimensional. --- test/framework/array.h | 4 ++++ test/framework/test_array2d.cpp | 14 ++++++++++++++ test/framework/types.h | 8 ++++++++ 3 files changed, 26 insertions(+) diff --git a/test/framework/array.h b/test/framework/array.h index 25a928523..55b8ba61c 100644 --- a/test/framework/array.h +++ b/test/framework/array.h @@ -17,6 +17,7 @@ #include #include "framework/abstract.h" +#include "framework/types.h" #include "framework/utils.h" namespace test { @@ -32,6 +33,9 @@ class Array2D : public TwoDimensional { explicit Array2D(size_t width, size_t height, size_t padding) : Array2D(width, height, padding, 1) {} + explicit Array2D(ArrayLayout layout) + : Array2D(layout.width, layout.height, layout.padding, layout.channels) {} + explicit Array2D(size_t width, size_t height, size_t padding, size_t channels) : width_{width}, height_{height}, diff --git a/test/framework/test_array2d.cpp b/test/framework/test_array2d.cpp index 5f1e51207..4bb94296b 100644 --- a/test/framework/test_array2d.cpp +++ b/test/framework/test_array2d.cpp @@ -36,6 +36,20 @@ TEST(Array2D, Constructor) { EXPECT_EQ_ARRAY2D(array_1, array_2); } +/// Tests that test::Array2D is constructible with test::ArrayLayout. +TEST(Array2D, ConstructFromArrayLayout) { + using ElementType = uint32_t; + + size_t width = 1, height = 2, padding = 3, channels = 4; + test::ArrayLayout layout{width, height, padding, channels}; + test::Array2D array{layout}; + EXPECT_EQ(array.width(), width); + EXPECT_EQ(array.height(), height); + EXPECT_EQ(array.channels(), channels); + EXPECT_EQ(array.stride(), width * sizeof(ElementType) + padding); + EXPECT_TRUE(array.valid()); +} + /// Tests that the copy assignment operator of test::Array2D works. TEST(Array2D, CopyAssignment) { size_t width = 5, height = 5; diff --git a/test/framework/types.h b/test/framework/types.h index 93e423091..10813a7aa 100644 --- a/test/framework/types.h +++ b/test/framework/types.h @@ -16,6 +16,14 @@ struct Point { size_t y; }; // end of struct Point +/// Describes the layout of a two-dimensional array. +struct ArrayLayout { + size_t width; + size_t height; + size_t padding; + size_t channels; +}; // end of struct ArrayLayout + } // namespace test #endif // INTRINSICCV_TEST_FRAMEWORK_TYPES_H_ -- GitLab From 3c1b2f4e6ee93b0328f54428651897923cffac9f Mon Sep 17 00:00:00 2001 From: Tamas Petz Date: Fri, 15 Dec 2023 18:12:02 +0100 Subject: [PATCH 10/14] [test] Add SequenceGenerator This generator produces values of an iterable object. --- test/framework/generator.h | 27 +++++++++++++++++++++++++++ test/framework/test_generator.cpp | 18 ++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/test/framework/generator.h b/test/framework/generator.h index b36c29233..34c56af53 100644 --- a/test/framework/generator.h +++ b/test/framework/generator.h @@ -31,6 +31,33 @@ class PseudoRandomNumberGenerator : public Generator { std::mt19937_64 rng_; }; // end of class PseudoRandomNumberGenerator +/// Generator which yields values of an iterable container. +template +class SequenceGenerator : public Generator { + public: + explicit SequenceGenerator(const IterableType &container) + : begin_{container.begin()}, + current_{container.begin()}, + end_{container.end()} {} + + /// Resets the generator to its initial state. + void reset() override { current_ = begin_; } + + /// Yields the next value or std::nullopt. + std::optional next() override { + if (current_ == end_) { + return std::nullopt; + } + + return *current_++; + } + + protected: + typename IterableType::const_iterator begin_; + typename IterableType::const_iterator current_; + typename IterableType::const_iterator end_; +}; // end of class SequenceGenerator + } // namespace test #endif // INTRINSICCV_TEST_FRAMEWORK_GENERATOR_H_ diff --git a/test/framework/test_generator.cpp b/test/framework/test_generator.cpp index 6bcd8e86e..a46b036a6 100644 --- a/test/framework/test_generator.cpp +++ b/test/framework/test_generator.cpp @@ -4,6 +4,9 @@ #include +#include +#include + #include "framework/generator.h" /// Tests test::PseudoRandomNumberGenerator::reset() works. @@ -17,3 +20,18 @@ TEST(PseudoRandomNumberGenerator, Reset) { ElementType value = generator.next().value(); EXPECT_EQ(initial_value, value); } + +/// Tests test::SequenceGenerator::reset() works. +TEST(SequenceGenerator, Reset) { + using ElementType = uint8_t; + + std::array array{1, 2, 3}; + test::SequenceGenerator generator{array}; + EXPECT_EQ(generator.next().value(), 1); + EXPECT_EQ(generator.next().value(), 2); + EXPECT_EQ(generator.next().value(), 3); + EXPECT_EQ(generator.next(), std::nullopt); + + generator.reset(); + EXPECT_EQ(generator.next().value(), 1); +} -- GitLab From fda683fab60dd8ec24da2995196f678aa1a3f56d Mon Sep 17 00:00:00 2001 From: Tamas Petz Date: Fri, 15 Dec 2023 18:22:34 +0100 Subject: [PATCH 11/14] [test] Add Array2D.fill() with Generator --- test/framework/array.h | 18 ++++++++++++++++++ test/framework/test_array2d.cpp | 23 ++++++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/test/framework/array.h b/test/framework/array.h index 55b8ba61c..c3655f777 100644 --- a/test/framework/array.h +++ b/test/framework/array.h @@ -97,6 +97,24 @@ class Array2D : public TwoDimensional { } } + /// Fills the underlying memory range with a given generator skipping padding + /// bytes. + void fill(Generator *generator) { + ASSERT_NE(generator, nullptr); + ASSERT_EQ(valid(), true); + + ElementType *ptr = data(); + for (size_t row = 0; row < height(); ++row) { + for (size_t column = 0; column < width(); ++column) { + std::optional optional_value = generator->next(); + ASSERT_NE(optional_value, std::nullopt); + ptr[column] = optional_value.value(); + } + + ptr = add_stride(ptr, 1); + } + } + /// Sets values in a row starting at a given column. void set(size_t row, size_t column, std::initializer_list values) { diff --git a/test/framework/test_array2d.cpp b/test/framework/test_array2d.cpp index 4bb94296b..f0854718f 100644 --- a/test/framework/test_array2d.cpp +++ b/test/framework/test_array2d.cpp @@ -5,9 +5,11 @@ #include #include +#include #include #include "framework/array.h" +#include "framework/generator.h" #include "framework/utils.h" /// Tests that the default constructor of test::Array2D default constructor @@ -120,7 +122,7 @@ TEST(Array2D, Fill) { test::Array2D array_1{width, height}; test::Array2D array_2{width, height}; - array_1.fill(0); + array_1.fill(uint32_t{0}); for (size_t row = 0; row < height; ++row) { for (size_t column = 0; column < width; ++column) { EXPECT_EQ(array_1.at(row, column)[0], 0); @@ -135,6 +137,25 @@ TEST(Array2D, Fill) { } } +/// Tests that test::Array2D.fill(Generator *) works. +TEST(Array2D, FillWithGenerator) { + using ElementType = uint32_t; + + const size_t width = 3, height = 2; + test::Array2D array{width, height}; + + const size_t num_elements = width * height; + std::array elements = {11, 12, 13, 14, 15, 16}; + test::SequenceGenerator generator{elements}; + array.fill(&generator); + EXPECT_EQ(array.at(0, 0)[0], 11); + EXPECT_EQ(array.at(0, 1)[0], 12); + EXPECT_EQ(array.at(0, 2)[0], 13); + EXPECT_EQ(array.at(1, 0)[0], 14); + EXPECT_EQ(array.at(1, 1)[0], 15); + EXPECT_EQ(array.at(1, 2)[0], 16); +} + /// Tests that EXPECT_EQ_ARRAY2D() macro works for fully-equal objects. TEST(Array2D, ExpectEq_Equal) { size_t width = 5, height = 2; -- GitLab From 578488e5da7fbef727f771ba12aacf9dd7d235da Mon Sep 17 00:00:00 2001 From: Tamas Petz Date: Fri, 15 Dec 2023 18:27:11 +0100 Subject: [PATCH 12/14] [test] Add Array2D::set() with TwoDimensional --- test/framework/array.h | 19 ++++++++++ test/framework/test_array2d.cpp | 61 +++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/test/framework/array.h b/test/framework/array.h index c3655f777..2cb2a2efa 100644 --- a/test/framework/array.h +++ b/test/framework/array.h @@ -132,6 +132,25 @@ class Array2D : public TwoDimensional { } } + /// Sets values starting in a given row starting at a given column. + /// + /// The layout of the input TwoDimensional object is not altered, meaning that + /// it must fit as-is at the given row and column position into the array. + void set(size_t row, size_t column, + const TwoDimensional *elements) { + ASSERT_NE(elements, nullptr); + ASSERT_GE(width(), column); + ASSERT_GE(height(), row); + ASSERT_GE(width() - column, elements->width()); + ASSERT_GE(height() - row, elements->height()); + + for (size_t row_offset = 0; row_offset < elements->height(); ++row_offset) { + const ElementType *src = elements->at(row_offset, 0); + ElementType *dst = at(row + row_offset, column); + std::memcpy(dst, src, elements->width() * sizeof(ElementType)); + } + } + /// Compares two instances for equality considering only element bytes. /// Returns the location of the first mismatch, if any. std::optional> compare_to( diff --git a/test/framework/test_array2d.cpp b/test/framework/test_array2d.cpp index f0854718f..7aed9667a 100644 --- a/test/framework/test_array2d.cpp +++ b/test/framework/test_array2d.cpp @@ -116,6 +116,67 @@ TEST(Array2D, Set) { EXPECT_EQ(array_1.at(1, 0)[0], 11); } +/// Tests that test::Array2D.set() works for TwoDimensional instances. +TEST(Array2D, SetWithTwoDimensional) { + using ElementType = uint32_t; + + // array_1: + // 0 1 2 + // 3 4 5 + // 6 7 8 + test::Array2D array_1{3, 3}; + array_1.set(0, 0, {0, 1, 2}); + array_1.set(1, 0, {3, 4, 5}); + array_1.set(2, 0, {6, 7, 8}); + + // array_2: + // 9 9 9 9 9 + // 9 9 9 9 9 + // 9 9 9 9 9 + // 9 9 9 9 9 + // 9 9 9 9 9 + test::Array2D array_2{5, 5}; + array_2.fill(9); + + // Expected array_2: + // 9 9 9 9 9 + // 9 9 0 1 2 + // 9 9 3 4 5 + // 9 9 6 7 8 + // 9 9 9 9 9 + array_2.set(1, 2, &array_1); + + EXPECT_EQ(array_2.at(0, 0)[0], 9); + EXPECT_EQ(array_2.at(0, 1)[0], 9); + EXPECT_EQ(array_2.at(0, 2)[0], 9); + EXPECT_EQ(array_2.at(0, 3)[0], 9); + EXPECT_EQ(array_2.at(0, 4)[0], 9); + + EXPECT_EQ(array_2.at(1, 0)[0], 9); + EXPECT_EQ(array_2.at(1, 1)[0], 9); + EXPECT_EQ(array_2.at(1, 2)[0], 0); + EXPECT_EQ(array_2.at(1, 3)[0], 1); + EXPECT_EQ(array_2.at(1, 4)[0], 2); + + EXPECT_EQ(array_2.at(2, 0)[0], 9); + EXPECT_EQ(array_2.at(2, 1)[0], 9); + EXPECT_EQ(array_2.at(2, 2)[0], 3); + EXPECT_EQ(array_2.at(2, 3)[0], 4); + EXPECT_EQ(array_2.at(2, 4)[0], 5); + + EXPECT_EQ(array_2.at(3, 0)[0], 9); + EXPECT_EQ(array_2.at(3, 1)[0], 9); + EXPECT_EQ(array_2.at(3, 2)[0], 6); + EXPECT_EQ(array_2.at(3, 3)[0], 7); + EXPECT_EQ(array_2.at(3, 4)[0], 8); + + EXPECT_EQ(array_2.at(4, 0)[0], 9); + EXPECT_EQ(array_2.at(4, 1)[0], 9); + EXPECT_EQ(array_2.at(4, 2)[0], 9); + EXPECT_EQ(array_2.at(4, 3)[0], 9); + EXPECT_EQ(array_2.at(4, 4)[0], 9); +} + /// Tests that test::Array2D.fill() works. TEST(Array2D, Fill) { size_t width = 5, height = 2; -- GitLab From 43f5e6df02e036dddc024a3127ff6c60038db169 Mon Sep 17 00:00:00 2001 From: Tamas Petz Date: Fri, 15 Dec 2023 19:51:07 +0100 Subject: [PATCH 13/14] [test] Add KernelTest This class drives kernel testing so that it - takes a number of kernels, - some border types, - array layouts to test for, and - a generator for elements. It executes the Cartesian Product of all the above. --- test/framework/kernel.h | 209 +++++++++++++++++++++++++++++++++ test/framework/test_kernel.cpp | 108 +++++++++++++++++ test/framework/utils.h | 6 + 3 files changed, 323 insertions(+) diff --git a/test/framework/kernel.h b/test/framework/kernel.h index a0d707b96..d88e63053 100644 --- a/test/framework/kernel.h +++ b/test/framework/kernel.h @@ -5,8 +5,16 @@ #ifndef INTRINSICCV_TEST_FRAMEWORK_KERNEL_H_ #define INTRINSICCV_TEST_FRAMEWORK_KERNEL_H_ +#include +#include + +#include +#include + #include "framework/abstract.h" #include "framework/array.h" +#include "framework/border.h" +#include "framework/generator.h" #include "framework/types.h" namespace test { @@ -49,6 +57,207 @@ class Kernel : protected Array2D, public Bordered { Point anchor_; }; // end of class Kernel +/// Abstract class to help implement a kernel operation. +/// +/// Required: +/// - KernelTestParams::InputType: Input type to the operation. +/// - KernelTestParams::IntermediateType: Type which is used during element +/// calculations. +/// - KernelTestParams::OutputType: Output type of the operation. +template +class KernelTest { + public: + using InputType = typename KernelTestParams::InputType; + using IntermediateType = typename KernelTestParams::IntermediateType; + using OutputType = typename KernelTestParams::OutputType; + + KernelTest() : debug_{false} {} + + /// Enables debug mode. + KernelTest &with_debug() { + debug_ = true; + return *this; + } + + void test(Generator> *kernel_generator, + Generator *array_layout_generator, + Generator *border_type_generator, + Generator *element_generator) { + ASSERT_NE(kernel_generator, nullptr); + kernel_generator->reset(); + + std::optional> maybe_kernel; + while ((maybe_kernel = kernel_generator->next()) != std::nullopt) { + test(*maybe_kernel, array_layout_generator, border_type_generator, + element_generator); + ASSERT_NO_FAILURES(); + } + } + + void test(Kernel kernel, + Generator *array_layout_generator, + Generator *border_type_generator, + Generator *element_generator) { + ASSERT_NE(array_layout_generator, nullptr); + array_layout_generator->reset(); + + std::optional maybe_array_layout; + while ((maybe_array_layout = array_layout_generator->next()) != + std::nullopt) { + ArrayLayout array_layout = *maybe_array_layout; + create_arrays(kernel, array_layout); + ASSERT_NO_FAILURES(); + test(kernel, array_layout, border_type_generator, element_generator); + ASSERT_NO_FAILURES(); + } + } + + void test(Kernel kernel, ArrayLayout array_layout, + Generator *border_type_generator, + Generator *element_generator) { + ASSERT_NE(border_type_generator, nullptr); + border_type_generator->reset(); + + std::optional maybe_border_type; + while ((maybe_border_type = border_type_generator->next()) != + std::nullopt) { + test(kernel, array_layout, *maybe_border_type, element_generator); + ASSERT_NO_FAILURES(); + } + } + + void test(Kernel kernel, ArrayLayout array_layout, + intrinsiccv_border_type_t border_type, + Generator *element_generator) { + prepare_source(element_generator); + prepare_expected(kernel, array_layout, border_type); + prepare_actual(); + this->call_api(&input_, &actual_, border_type); + check_results(); + } + + protected: + /// Calls the API-under-test in the appropriate way. + /// + /// The arguments are never nullptr. + virtual void call_api(const Array2D *input, + Array2D *output, + intrinsiccv_border_type_t border_type) = 0; + + /// Calculates the expected output. + virtual void calculate_expected(const Kernel &kernel, + const TwoDimensional &source) { + for (size_t row = 0; row < expected_.height(); ++row) { + for (size_t column = 0; column < expected_.width(); ++column) { + IntermediateType result; + result = calculate_expected_at(kernel, source, row, column); + expected_.at(row, column)[0] = static_cast(result); + } + } + } + + /// Calculates the expected element at a given position. + virtual IntermediateType calculate_expected_at( + const Kernel &kernel, + const TwoDimensional &source, size_t row, size_t column) { + IntermediateType result{0}; + for (size_t height = 0; height < kernel.height(); ++height) { + for (size_t width = 0; width < kernel.width(); ++width) { + IntermediateType coefficient = kernel.at(height, width)[0]; + InputType value = + source.at(row + height, column + width * source.channels())[0]; + result += coefficient * static_cast(value); + } + } + + return result; + } + + /// Creates arrays for a given layout. + void create_arrays(const Kernel &kernel, + const ArrayLayout &array_layout) { + input_ = Array2D{array_layout}; + ASSERT_TRUE(input_.valid()); + + expected_ = Array2D{array_layout}; + ASSERT_TRUE(expected_.valid()); + + actual_ = Array2D{array_layout}; + ASSERT_TRUE(actual_.valid()); + + input_with_borders_ = Array2D{ + array_layout.width + + (kernel.left() + kernel.right()) * array_layout.channels, + array_layout.height + kernel.top() + kernel.bottom(), 0, + array_layout.channels}; + ASSERT_TRUE(input_with_borders_.valid()); + } + + /// Prepares input to the kernel-based operation. + void prepare_source(Generator *element_generator) { + ASSERT_NE(element_generator, nullptr); + element_generator->reset(); + input_.fill(element_generator); + + if (debug_) { + std::cout << "[source]" << std::endl; + dump(&input_); + } + } + + /// Computes expected output of the kernel-based operation. + void prepare_expected(const Kernel &kernel, + const ArrayLayout &array_layout, + intrinsiccv_border_type_t border_type) { + input_with_borders_.set(kernel.anchor().x, + kernel.anchor().y * array_layout.channels, &input_); + + if (debug_) { + std::cout << "[input_with_borders without borders]" << std::endl; + dump(&input_with_borders_); + } + + prepare_borders(border_type, &kernel, &input_with_borders_); + + if (debug_) { + std::cout << "[input_with_borders with borders]" << std::endl; + dump(&input_with_borders_); + } + + calculate_expected(kernel, input_with_borders_); + + if (debug_) { + std::cout << "[expected]" << std::endl; + dump(&expected_); + } + } + + /// Prepares the actual output of the kernel-based operation. + void prepare_actual() { actual_.fill(42); } + + /// Checks that the actual output matches the expectations. + void check_results() { + if (debug_) { + std::cout << "[actual]" << std::endl; + dump(&actual_); + } + + // Check that the actual result matches the expectation. + EXPECT_EQ_ARRAY2D(expected_, actual_); + } + + /// Input operand for the operation. + Array2D input_; + /// Input operand with borders, used to calculate expeected values. + Array2D input_with_borders_; + /// Expected result of the operation. + Array2D expected_; + /// Actual result of the operation. + Array2D actual_; + /// Enables debug mode. + bool debug_; +}; // end of class KernelTest + } // namespace test #endif // INTRINSICCV_TEST_FRAMEWORK_KERNEL_H_ diff --git a/test/framework/test_kernel.cpp b/test/framework/test_kernel.cpp index e5426d4e5..2818859a7 100644 --- a/test/framework/test_kernel.cpp +++ b/test/framework/test_kernel.cpp @@ -3,8 +3,12 @@ // SPDX-License-Identifier: Apache-2.0 #include +#include + +#include #include "framework/kernel.h" +#include "framework/types.h" /// Tests that the constructor of test::Kernel works for odd width and /// height. @@ -74,3 +78,107 @@ TEST(Kernel, ConstructEmpty) { EXPECT_EQ(kernel.bottom(), 0); EXPECT_EQ(kernel.right(), 0); } + +class KernelTestParams { + public: + using InputType = uint8_t; + using IntermediateType = int32_t; + using OutputType = int16_t; +}; // end of class KernelTestParams + +template +class ExampleKernelTest : public test::KernelTest { + using InputType = typename KernelTestParams::InputType; + using IntermediateType = typename KernelTestParams::IntermediateType; + using OutputType = typename KernelTestParams::OutputType; + + void call_api(const test::Array2D *input, + test::Array2D *output, + intrinsiccv_border_type_t border_type) override { + ++api_calls_; + + // Check the expected border type. + EXPECT_EQ(border_type, kBorders[border_count_ % kBorders.size()]); + ++border_count_; + if (border_count_ == kBorders.size()) { + border_count_ = 0; + } + + // Check the expected layout. + const test::ArrayLayout &expected_array_layout = + kArrayLayouts[array_layouts_ % kArrayLayouts.size()]; + + EXPECT_EQ(expected_array_layout.width, input->width()); + EXPECT_EQ(expected_array_layout.height, input->height()); + EXPECT_EQ(expected_array_layout.padding, + input->stride() - input->width() * sizeof(InputType)); + EXPECT_EQ(expected_array_layout.channels, input->channels()); + + EXPECT_EQ(input->width(), output->width()); + EXPECT_EQ(input->height(), output->height()); + // NOTE: This is not expected. + // EXPECT_EQ(input->stride(), output->stride()); + EXPECT_EQ(input->channels(), output->channels()); + + if (border_count_ == 0) { + ++array_layouts_; + } + + // Fake some result. + output->fill(OutputType{0}); + } + + public: + static const std::array, 2> kKernels; + + static constexpr std::array kArrayLayouts = {{ + {3, 2, 10, 1}, + {6, 5, 11, 2}, + {9, 7, 11, 3}, + }}; + + static constexpr std::array kBorders = { + // NOTE: At the time of writing this test only replicate was implemented. + INTRINSICCV_BORDER_TYPE_REPLICATE, + INTRINSICCV_BORDER_TYPE_REPLICATE, + INTRINSICCV_BORDER_TYPE_REPLICATE, + INTRINSICCV_BORDER_TYPE_REPLICATE, + }; + + size_t api_calls_{0}; + size_t array_layouts_{0}; + size_t border_count_{0}; +}; // end of class class ExampleKernelTest + +template +const std::array, 2> + ExampleKernelTest::kKernels = { + test::Kernel{ + test::Array2D{3, 3}}, + test::Kernel{ + test::Array2D{4, 4}}, +}; + +/// Tests that KernelTest::test() works. +TEST(KernelTest, Test) { + test::SequenceGenerator tested_kernels{ + ExampleKernelTest::kKernels}; + test::SequenceGenerator tested_borders{ + ExampleKernelTest::kBorders}; + test::SequenceGenerator tested_array_layouts{ + ExampleKernelTest::kArrayLayouts}; + test::PseudoRandomNumberGenerator + element_generator; + + ExampleKernelTest kernel_test; + kernel_test.test(&tested_kernels, &tested_array_layouts, &tested_borders, + &element_generator); + + EXPECT_EQ(kernel_test.api_calls_, + ExampleKernelTest::kKernels.size() * + ExampleKernelTest::kArrayLayouts.size() * + ExampleKernelTest::kBorders.size()); + EXPECT_EQ(kernel_test.array_layouts_, + ExampleKernelTest::kKernels.size() * + ExampleKernelTest::kArrayLayouts.size()); +} diff --git a/test/framework/utils.h b/test/framework/utils.h index fa7c35d22..5dcaf64ea 100644 --- a/test/framework/utils.h +++ b/test/framework/utils.h @@ -28,6 +28,12 @@ return (return_value); \ } while (0 != 0) +// Returns if the test has any failures. +#define ASSERT_NO_FAILURES() \ + if (::testing::Test::HasFailure()) { \ + return; \ + } + namespace test { class Options { -- GitLab From 61e67b7f9f3eddc9bb32d621f3826f55d55ef61a Mon Sep 17 00:00:00 2001 From: Tamas Petz Date: Fri, 15 Dec 2023 19:53:00 +0100 Subject: [PATCH 14/14] [test] Add API tests for Sobel operators The tests use default layouts to test for, inputs are filled with pseudorandom numbers. --- test/api/test_sobel.cpp | 103 +++++++++++++++++++++++++++++++++++++++ test/framework/utils.cpp | 25 ++++++++++ test/framework/utils.h | 5 ++ 3 files changed, 133 insertions(+) create mode 100644 test/api/test_sobel.cpp diff --git a/test/api/test_sobel.cpp b/test/api/test_sobel.cpp new file mode 100644 index 000000000..0c5d8a7c1 --- /dev/null +++ b/test/api/test_sobel.cpp @@ -0,0 +1,103 @@ +// SPDX-FileCopyrightText: 2023 Arm Limited and/or its affiliates +// +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +#include + +#include "framework/array.h" +#include "framework/generator.h" +#include "framework/kernel.h" + +#define INTRINSICCV_SOBEL_3X3_HORIZONTAL(type, suffix) \ + INTRINSICCV_API(sobel_3x3_horizontal, \ + intrinsiccv_sobel_3x3_horizontal_##suffix, type) + +#define INTRINSICCV_SOBEL_3X3_VERTICAL(type, suffix) \ + INTRINSICCV_API(sobel_3x3_vertical, intrinsiccv_sobel_3x3_vertical_##suffix, \ + type) + +INTRINSICCV_SOBEL_3X3_HORIZONTAL(uint8_t, s16_u8); + +INTRINSICCV_SOBEL_3X3_VERTICAL(uint8_t, s16_u8); + +/// Implements KernelTestParams for Sobel operators. +template +struct SobelKernelTestParams; + +template +struct SobelKernelTestParams { + using InputType = uint8_t; + using IntermediateType = int16_t; + using OutputType = int16_t; + + static constexpr bool kIsHorizontal = IsHorizontal; +}; // end of struct SobelKernelTestParams + +static constexpr std::array kSupportedBorders = { + INTRINSICCV_BORDER_TYPE_REPLICATE, +}; + +/// Default test fo horizontal Sobel 3x3 operator. +template +class Sobel3x3Test : public test::KernelTest { + using typename test::KernelTest::InputType; + using typename test::KernelTest::IntermediateType; + using typename test::KernelTest::OutputType; + + void call_api(const test::Array2D *input, + test::Array2D *output, + intrinsiccv_border_type_t) override { + auto api = KernelTestParams::kIsHorizontal + ? sobel_3x3_horizontal() + : sobel_3x3_vertical(); + api(input->data(), input->stride(), output->data(), output->stride(), + input->width() / input->channels(), input->height(), input->channels()); + } + + public: + void test(test::Array2D mask) { + test::Kernel kernel{mask}; + // Use the default array layouts for testing. + auto array_layouts = + test::default_array_layouts(mask.width(), mask.height()); + // Create generators and execute test. + test::SequenceGenerator tested_borders{kSupportedBorders}; + test::SequenceGenerator tested_array_layouts{array_layouts}; + test::PseudoRandomNumberGenerator element_generator; + this->test::KernelTest::test( + kernel, &tested_array_layouts, &tested_borders, &element_generator); + } +}; // end of class class Sobel3x3Test + +template +class Sobel : public testing::Test {}; + +using ElementTypes = ::testing::Types; +TYPED_TEST_SUITE(Sobel, ElementTypes); + +/// Tests sobel_3x3_horizontal__ API. +TYPED_TEST(Sobel, Horizontal3x3) { + using KernelTestParams = SobelKernelTestParams; + // Horizontal 3x3 Sobel operator. + test::Array2D mask{3, 3}; + mask.set(0, 0, {-1, 0, 1}); + mask.set(1, 0, {-2, 0, 2}); + mask.set(2, 0, {-1, 0, 1}); + Sobel3x3Test{}.test(mask); +} + +/// Tests sobel_3x3_vertical__ API. +TYPED_TEST(Sobel, Vertical3x3) { + using KernelTestParams = SobelKernelTestParams; + // Horizontal 3x3 Sobel operator. + test::Array2D mask{3, 3}; + // clang-format off + mask.set(0, 0, {-1, -2, -1}); + mask.set(1, 0, { 0, 0, 0}); + mask.set(2, 0, { 1, 2, 1}); + // clang-format on + Sobel3x3Test{}.test(mask); +} diff --git a/test/framework/utils.cpp b/test/framework/utils.cpp index 889ee3fff..0640bb2e0 100644 --- a/test/framework/utils.cpp +++ b/test/framework/utils.cpp @@ -6,6 +6,7 @@ #include +#include #include #include #include @@ -47,4 +48,28 @@ template void dump(const TwoDimensional *); template void dump(const TwoDimensional *); template void dump(const TwoDimensional *); +std::array default_array_layouts(size_t min_width, + size_t min_height) { + size_t vl = test::Options::vector_length(); + size_t width = std::max(min_width, vl); + + return {{ + // clang-format off + // width, height, padding, channels + { min_width, min_height, 0, 1}, + { min_width * 2, min_height, 0, 2}, + { min_width * 3, min_height, 0, 3}, + { min_width, min_height, vl, 1}, + { min_width * 2, min_height, vl, 2}, + { min_width * 3, min_height, vl, 3}, + { width - 1, min_height, 0, 1}, + {2 * (width - 1), min_height + 1, 0, 2}, + {3 * (width - 1), min_height + 2, 0, 3}, + { width - 1, min_height, vl, 1}, + {2 * (width - 1), min_height + 1, vl, 2}, + {3 * (width - 1), min_height + 2, vl, 3}, + // clang-format on + }}; +} + } // namespace test diff --git a/test/framework/utils.h b/test/framework/utils.h index 5dcaf64ea..c3ee3baca 100644 --- a/test/framework/utils.h +++ b/test/framework/utils.h @@ -11,6 +11,7 @@ #include #include "framework/abstract.h" +#include "framework/types.h" #define INTRINSICCV_API(name, impl, type) \ template void dump(const TwoDimensional *elements); +/// Returns an array of default tested layouts. +std::array default_array_layouts(size_t min_width, + size_t min_height); + } // namespace test #endif // INTRINSICCV_TEST_FRAMEWORK_UTILS_H_ -- GitLab