// Copyright © Advanced Micro Devices, Inc., or its affiliates. // SPDX-License-Identifier: MIT #include #include #include #include #include using namespace hipdnn_data_sdk::utilities; // ============================================================================ // Basic Typed Iteration Tests // ============================================================================ TEST(TestTensorView, BasicIteration) { Tensor tensor({2, 3}); tensor.fillWithValue(1.0f); ITensor* iTensor = &tensor; TensorView view(*iTensor); int count = 0; for(auto it = view.begin(); it != view.end(); ++it) { // No cast needed! Direct typed reference const float& value = *it; EXPECT_EQ(value, 1.0f); ++count; } EXPECT_EQ(count, 6); } TEST(TestTensorView, ModifyValues) { Tensor tensor({2, 3}); tensor.fillWithValue(1.0f); ITensor* iTensor = &tensor; TensorView view(*iTensor); // Modify all values through typed iterator for(auto it = view.begin(); it != view.end(); ++it) { float& value = *it; value = 5.0f; } // Verify modifications for(auto it = view.begin(); it != view.end(); ++it) { EXPECT_EQ(*it, 5.0f); } } TEST(TestTensorView, RangeBasedForLoop) { Tensor tensor({2, 2}); tensor.fillWithValue(3.14f); ITensor* iTensor = &tensor; TensorView view(*iTensor); int count = 0; // Clean range-based for loop without casts for(float& value : view) { EXPECT_FLOAT_EQ(value, 3.14f); value = 2.71f; ++count; } EXPECT_EQ(count, 4); // Verify modifications for(const float& value : view) { EXPECT_FLOAT_EQ(value, 2.71f); } } // ============================================================================ // Const Correctness Tests // ============================================================================ TEST(TestTensorView, ConstIteration) { Tensor tensor({2, 2}); tensor.fillWithValue(3.14); const ITensor* iTensor = &tensor; const ConstTensorView view(*iTensor); int count = 0; for(auto it = view.cbegin(); it != view.cend(); ++it) { const double& value = *it; EXPECT_DOUBLE_EQ(value, 3.14); ++count; } EXPECT_EQ(count, 4); } TEST(TestTensorView, ConstViewFromConstTensor) { Tensor tensor({2, 3}); tensor.fillWithValue(1.5f); const ITensor& iTensor = tensor; ConstTensorView view(iTensor); // NOLINT(misc-const-correctness) begin() not const // Should be able to read through const view for(const float& value : view) { EXPECT_FLOAT_EQ(value, 1.5f); } } TEST(TestTensorView, ConstRangeBasedForLoop) { Tensor tensor({3, 3}); tensor.fillWithValue(42.0f); ITensor* iTensor = &tensor; ConstTensorView view(*iTensor); // NOLINT(misc-const-correctness) begin() not const int count = 0; // NOLINT(misc-const-correctness) mutated in loop for(const int& value : view) { EXPECT_EQ(value, 42); ++count; } EXPECT_EQ(count, 9); } // ============================================================================ // Iterator Comparison Tests // ============================================================================ TEST(TestTensorView, EqualityComparison) { Tensor tensor({2, 2}); ITensor* iTensor = &tensor; TensorView view(*iTensor); auto it1 = view.begin(); auto it2 = view.begin(); EXPECT_TRUE(it1 == it2); EXPECT_FALSE(it1 != it2); ++it1; EXPECT_FALSE(it1 == it2); EXPECT_TRUE(it1 != it2); ++it2; EXPECT_TRUE(it1 == it2); } TEST(TestTensorView, EndComparison) { Tensor tensor({2, 2}); ITensor* iTensor = &tensor; TensorView view(*iTensor); auto it = view.begin(); auto end = view.end(); EXPECT_NE(it, end); // Advance to end for(int i = 0; i < 4; ++i) { ++it; } EXPECT_EQ(it, end); } // ============================================================================ // Copy and Move Semantics Tests // ============================================================================ TEST(TestTensorView, CopyConstructor) { Tensor tensor({2, 2}); tensor.fillWithValue(2.0f); ITensor* iTensor = &tensor; TensorView view(*iTensor); auto it1 = view.begin(); // NOLINTNEXTLINE(performance-unnecessary-copy-initialization) auto it2 = it1; // Copy const float& val1 = *it1; const float& val2 = *it2; EXPECT_EQ(&val1, &val2); EXPECT_EQ(val1, 2.0f); } TEST(TestTensorView, CopyAssignment) { Tensor tensor({2, 2}); tensor.fillWithValue(3.0f); ITensor* iTensor = &tensor; TensorView view(*iTensor); auto it1 = view.begin(); auto it2 = view.end(); it2 = it1; // Copy assignment EXPECT_EQ(it1, it2); } TEST(TestTensorView, MoveConstructor) { Tensor tensor({2, 2}); tensor.fillWithValue(4.0f); ITensor* iTensor = &tensor; TensorView view(*iTensor); auto it1 = view.begin(); auto it2 = std::move(it1); // Move const float& val = *it2; EXPECT_EQ(val, 4.0f); } TEST(TestTensorView, ViewCopy) { Tensor tensor({2, 2}); tensor.fillWithValue(1.0f); ITensor* iTensor = &tensor; TensorView view1(*iTensor); TensorView view2(view1); // Copy view // Both views should iterate the same tensor auto it1 = view1.begin(); auto it2 = view2.begin(); EXPECT_EQ(*it1, *it2); } // ============================================================================ // Type Safety Tests // ============================================================================ TEST(TestTensorView, AutoTypeDeduction) { Tensor tensor({2, 2}); tensor.fillWithValue(1.5f); ITensor* iTensor = &tensor; TensorView view(*iTensor); // auto should correctly deduce float& for(auto& value : view) { static_assert(std::is_same_v, "Type should be float&"); } } // ============================================================================ // Different Data Types // ============================================================================ TEST(TestTensorViewDouble, BasicIteration) { Tensor tensor({3, 3}); tensor.fillWithValue(2.718); ITensor* iTensor = &tensor; TensorView view(*iTensor); for(const double& value : view) { EXPECT_DOUBLE_EQ(value, 2.718); } } TEST(TestTensorViewInt, BasicIteration) { Tensor tensor({4, 4}); tensor.fillWithValue(7.0f); ITensor* iTensor = &tensor; TensorView view(*iTensor); for(const int& value : view) { EXPECT_EQ(value, 7); } } // ============================================================================ // Strided Tensor Tests // ============================================================================ TEST(TestTensorView, StridedTensor) { const std::vector dims = {2, 2}; const std::vector strides = {3, 1}; // Non-standard strides Tensor tensor(dims, strides); ITensor* iTensor = &tensor; TensorView view(*iTensor); // Set values using view int counter = 0; for(float& value : view) { value = static_cast(counter++); } // Verify using indices EXPECT_EQ(tensor.getHostValue(0, 0), 0.0f); EXPECT_EQ(tensor.getHostValue(0, 1), 1.0f); EXPECT_EQ(tensor.getHostValue(1, 0), 2.0f); EXPECT_EQ(tensor.getHostValue(1, 1), 3.0f); } // ============================================================================ // Multi-Dimensional Tests // ============================================================================ TEST(TestTensorView, TwoDimensionalTensor) { Tensor tensor({3, 4}); ITensor* iTensor = &tensor; TensorView view(*iTensor); // Fill with sequence int counter = 0; for(float& value : view) { value = static_cast(counter++); } EXPECT_EQ(counter, 12); // Verify a few values EXPECT_EQ(tensor.getHostValue(0, 0), 0.0f); EXPECT_EQ(tensor.getHostValue(2, 3), 11.0f); } TEST(TestTensorView, ThreeDimensionalTensor) { Tensor tensor({2, 3, 4}); ITensor* iTensor = &tensor; TensorView view(*iTensor); // Fill with index-based values int counter = 0; for(float& value : view) { value = static_cast(counter++); } EXPECT_EQ(counter, 24); // Verify first and last elements EXPECT_EQ(tensor.getHostValue(0, 0, 0), 0.0f); EXPECT_EQ(tensor.getHostValue(1, 2, 3), 23.0f); } TEST(TestTensorView, FourDimensionalTensor) { Tensor tensor({2, 2, 2, 2}); ITensor* iTensor = &tensor; TensorView view(*iTensor); int count = 0; for(int& value : view) { value = count++; } EXPECT_EQ(count, 16); EXPECT_EQ(tensor.getHostValue(0, 0, 0, 0), 0.0f); EXPECT_EQ(tensor.getHostValue(1, 1, 1, 1), 15.0f); } // ============================================================================ // Edge Cases // ============================================================================ TEST(TestTensorView, EmptyTensor) { const std::vector dims = {}; // Zero dimensions Tensor tensor(dims); ITensor* iTensor = &tensor; TensorView view(*iTensor); auto it = view.begin(); EXPECT_EQ(it, view.end()); EXPECT_THROW(it++, std::out_of_range); } TEST(TestTensorView, SingleElement) { Tensor tensor({1}); tensor.setHostValue(42, 0); TensorView view(tensor); int count = 0; for(const int& value : view) { EXPECT_EQ(value, 42); ++count; } EXPECT_EQ(count, 1); } // ============================================================================ // Interoperability Tests // ============================================================================ TEST(TestTensorView, FromITensorReference) { Tensor tensor({2, 3}); tensor.fillWithValue(1.0f); ITensor& iTensor = tensor; TensorView view(iTensor); for(const float& value : view) { EXPECT_EQ(value, 1.0f); } } TEST(TestTensorView, FromConstITensorReference) { Tensor tensor({2, 3}); tensor.fillWithValue(2.0f); const ITensor& iTensor = tensor; ConstTensorView view(iTensor); // NOLINT(misc-const-correctness) begin() not const for(const float& value : view) { EXPECT_EQ(value, 2.0f); } } TEST(TestTensorView, ViewAndDirectAccess) { Tensor tensor({2, 2}); TensorView view(tensor); // Set values through view int counter = 0; for(float& value : view) { value = static_cast(counter++); } // Verify through direct tensor access EXPECT_EQ(tensor.getHostValue(0, 0), 0.0f); EXPECT_EQ(tensor.getHostValue(0, 1), 1.0f); EXPECT_EQ(tensor.getHostValue(1, 0), 2.0f); EXPECT_EQ(tensor.getHostValue(1, 1), 3.0f); } // ============================================================================ // STL Algorithm Compatibility // ============================================================================ TEST(TestTensorView, StdCount) { Tensor tensor({5}); TensorView view(tensor); // Fill with values int counter = 0; for(int& value : view) { value = (counter++ % 2 == 0) ? 1 : 2; } // Count occurrences of 1 auto count = std::count(view.begin(), view.end(), 1); EXPECT_EQ(count, 3); // Indices 0, 2, 4 } TEST(TestTensorView, StdAccumulate) { Tensor tensor({5}); TensorView view(tensor); // Fill with sequence 1, 2, 3, 4, 5 std::iota(view.begin(), view.end(), 1); // Sum all values const int sum = std::accumulate(view.begin(), view.end(), 0); EXPECT_EQ(sum, 15); // 1+2+3+4+5 } TEST(TestTensorView, StdTransform) { Tensor tensor({4}); TensorView view(tensor); // Fill with initial values std::iota(view.begin(), view.end(), 1.0f); // Double all values std::transform(view.begin(), view.end(), view.begin(), [](float val) { return val * 2.0f; }); // Verify int idx = 0; for(const float& value : view) { EXPECT_FLOAT_EQ(value, static_cast((idx + 1) * 2)); ++idx; } } TEST(TestTensorView, StdForEach) { Tensor tensor({3, 3}); TensorView view(tensor); // Initialize std::iota(view.begin(), view.end(), 1); // Multiply each by 3 std::for_each(view.begin(), view.end(), [](int& val) { val *= 3; }); // Verify int expected = 3; for(const int& value : view) { EXPECT_EQ(value, expected); expected += 3; } } // ============================================================================ // Prefix vs Postfix Increment // ============================================================================ TEST(TestTensorView, PrefixIncrement) { Tensor tensor({3}); tensor.setHostValue(10, 0); tensor.setHostValue(20, 1); tensor.setHostValue(30, 2); TensorView view(tensor); auto it = view.begin(); auto it2 = ++it; // Prefix increment // Both should point to same element const int& val1 = *it; const int& val2 = *it2; EXPECT_EQ(&val1, &val2); EXPECT_EQ(val1, 20); } TEST(TestTensorView, PostfixIncrement) { Tensor tensor({3}); tensor.setHostValue(10, 0); tensor.setHostValue(20, 1); tensor.setHostValue(30, 2); TensorView view(tensor); auto it = view.begin(); auto it2 = it++; // Postfix increment // it2 should point to old position const int& val2 = *it2; EXPECT_EQ(val2, 10); // it should point to new position const int& val1 = *it; EXPECT_EQ(val1, 20); }