Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJacques Lucke <jacques@blender.org>2020-07-26 13:19:11 +0300
committerJacques Lucke <jacques@blender.org>2020-07-26 13:19:11 +0300
commit6cecdf2adec0b824ec49204299b5a04dcdc124d9 (patch)
tree0abfe6ffb4d369f9acc3f79e96a77a0d1be4be4d /source/blender/functions
parent13abacaaf342b3cff9b29e92f346bee2e5b6e6c4 (diff)
Functions: move tests closer to code
Diffstat (limited to 'source/blender/functions')
-rw-r--r--source/blender/functions/CMakeLists.txt17
-rw-r--r--source/blender/functions/tests/FN_array_spans_test.cc132
-rw-r--r--source/blender/functions/tests/FN_attributes_ref_test.cc97
-rw-r--r--source/blender/functions/tests/FN_cpp_type_test.cc325
-rw-r--r--source/blender/functions/tests/FN_generic_vector_array_test.cc101
-rw-r--r--source/blender/functions/tests/FN_multi_function_network_test.cc253
-rw-r--r--source/blender/functions/tests/FN_multi_function_test.cc385
-rw-r--r--source/blender/functions/tests/FN_spans_test.cc214
8 files changed, 1524 insertions, 0 deletions
diff --git a/source/blender/functions/CMakeLists.txt b/source/blender/functions/CMakeLists.txt
index 2686275e898..ad29dbe6668 100644
--- a/source/blender/functions/CMakeLists.txt
+++ b/source/blender/functions/CMakeLists.txt
@@ -57,3 +57,20 @@ set(LIB
)
blender_add_lib(bf_functions "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
+
+if(WITH_GTESTS)
+ set(TEST_SRC
+ tests/FN_array_spans_test.cc
+ tests/FN_attributes_ref_test.cc
+ tests/FN_cpp_type_test.cc
+ tests/FN_generic_vector_array_test.cc
+ tests/FN_multi_function_network_test.cc
+ tests/FN_multi_function_test.cc
+ tests/FN_spans_test.cc
+ )
+ set (TEST_LIB
+ bf_functions
+ )
+ include(GTestTesting)
+ blender_add_test_lib(bf_functions_tests "${TEST_SRC}" "${INC};${TEST_INC}" "${INC_SYS}" "${LIB};${TEST_LIB}")
+endif()
diff --git a/source/blender/functions/tests/FN_array_spans_test.cc b/source/blender/functions/tests/FN_array_spans_test.cc
new file mode 100644
index 00000000000..9a632b58be8
--- /dev/null
+++ b/source/blender/functions/tests/FN_array_spans_test.cc
@@ -0,0 +1,132 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include "FN_array_spans.hh"
+#include "FN_generic_vector_array.hh"
+
+#include "BLI_array.hh"
+
+namespace blender::fn::tests {
+
+TEST(virtual_array_span, EmptyConstructor)
+{
+ VArraySpan<int> span;
+ EXPECT_EQ(span.size(), 0);
+ EXPECT_TRUE(span.is_empty());
+
+ GVArraySpan converted(span);
+ EXPECT_EQ(converted.type(), CPPType::get<int>());
+ EXPECT_EQ(converted.size(), 0);
+}
+
+TEST(virtual_array_span, SingleArrayConstructor)
+{
+ std::array<int, 4> values = {3, 4, 5, 6};
+ VArraySpan<int> span{Span<int>(values), 3};
+ EXPECT_EQ(span.size(), 3);
+ EXPECT_FALSE(span.is_empty());
+ EXPECT_EQ(span[0].size(), 4);
+ EXPECT_EQ(span[1].size(), 4);
+ EXPECT_EQ(span[2].size(), 4);
+ EXPECT_EQ(span[0][0], 3);
+ EXPECT_EQ(span[0][1], 4);
+ EXPECT_EQ(span[0][2], 5);
+ EXPECT_EQ(span[0][3], 6);
+ EXPECT_EQ(span[1][3], 6);
+ EXPECT_EQ(span[2][1], 4);
+
+ GVArraySpan converted(span);
+ EXPECT_EQ(converted.type(), CPPType::get<int>());
+ EXPECT_EQ(converted.size(), 3);
+ EXPECT_EQ(converted[0].size(), 4);
+ EXPECT_EQ(converted[1].size(), 4);
+ EXPECT_EQ(converted[1][2], &values[2]);
+}
+
+TEST(virtual_array_span, MultipleArrayConstructor)
+{
+ std::array<int, 4> values0 = {1, 2, 3, 4};
+ std::array<int, 2> values1 = {6, 7};
+ std::array<int, 1> values2 = {8};
+ std::array<const int *, 3> starts = {values0.data(), values1.data(), values2.data()};
+ std::array<int64_t, 3> sizes{values0.size(), values1.size(), values2.size()};
+
+ VArraySpan<int> span{starts, sizes};
+ EXPECT_EQ(span.size(), 3);
+ EXPECT_FALSE(span.is_empty());
+ EXPECT_EQ(span[0].size(), 4);
+ EXPECT_EQ(span[1].size(), 2);
+ EXPECT_EQ(span[2].size(), 1);
+ EXPECT_EQ(&span[0][0], values0.data());
+ EXPECT_EQ(&span[1][0], values1.data());
+ EXPECT_EQ(&span[2][0], values2.data());
+ EXPECT_EQ(span[2][0], 8);
+ EXPECT_EQ(span[1][1], 7);
+
+ GVArraySpan converted(span);
+ EXPECT_EQ(converted.type(), CPPType::get<int>());
+ EXPECT_EQ(converted.size(), 3);
+ EXPECT_EQ(converted[0].size(), 4);
+ EXPECT_EQ(converted[1].size(), 2);
+ EXPECT_EQ(converted[2].size(), 1);
+ EXPECT_EQ(converted[0][0], values0.data());
+ EXPECT_EQ(converted[1][1], values1.data() + 1);
+}
+
+TEST(generic_virtual_array_span, TypeConstructor)
+{
+ GVArraySpan span{CPPType::get<int32_t>()};
+ EXPECT_EQ(span.size(), 0);
+ EXPECT_TRUE(span.is_empty());
+
+ VArraySpan converted = span.typed<int>();
+ EXPECT_EQ(converted.size(), 0);
+}
+
+TEST(generic_virtual_array_span, GSpanConstructor)
+{
+ std::array<std::string, 3> values = {"hello", "world", "test"};
+ GVArraySpan span{GSpan(CPPType::get<std::string>(), values.data(), 3), 5};
+ EXPECT_EQ(span.size(), 5);
+ EXPECT_FALSE(span.is_empty());
+ EXPECT_EQ(span[0][0], values.data());
+ EXPECT_EQ(span[1][0], values.data());
+ EXPECT_EQ(span[4][0], values.data());
+ EXPECT_EQ(span[0].size(), 3);
+ EXPECT_EQ(span[2].size(), 3);
+ EXPECT_EQ(*(std::string *)span[3][1], "world");
+
+ VArraySpan converted = span.typed<std::string>();
+ EXPECT_EQ(converted.size(), 5);
+ EXPECT_EQ(converted[0][0], "hello");
+ EXPECT_EQ(converted[1][0], "hello");
+ EXPECT_EQ(converted[4][0], "hello");
+ EXPECT_EQ(converted[0].size(), 3);
+ EXPECT_EQ(converted[2].size(), 3);
+}
+
+TEST(generic_virtual_array_span, IsSingleArray1)
+{
+ Array<int> values = {5, 6, 7};
+ GVArraySpan span{GSpan(values.as_span()), 4};
+ EXPECT_TRUE(span.is_single_array());
+
+ VArraySpan converted = span.typed<int>();
+ EXPECT_TRUE(converted.is_single_array());
+}
+
+TEST(generic_virtual_array_span, IsSingleArray2)
+{
+ GVectorArray vectors{CPPType::get<int32_t>(), 3};
+ GVectorArrayRef<int> vectors_ref = vectors;
+ vectors_ref.append(1, 4);
+
+ GVArraySpan span = vectors;
+ EXPECT_FALSE(span.is_single_array());
+
+ VArraySpan converted = span.typed<int>();
+ EXPECT_FALSE(converted.is_single_array());
+}
+
+} // namespace blender::fn::tests
diff --git a/source/blender/functions/tests/FN_attributes_ref_test.cc b/source/blender/functions/tests/FN_attributes_ref_test.cc
new file mode 100644
index 00000000000..3a5e4743c1e
--- /dev/null
+++ b/source/blender/functions/tests/FN_attributes_ref_test.cc
@@ -0,0 +1,97 @@
+/* Apache License, Version 2.0 */
+
+#include "BLI_float3.hh"
+#include "FN_attributes_ref.hh"
+
+#include "testing/testing.h"
+
+namespace blender::fn::tests {
+
+TEST(attributes_info, BuildEmpty)
+{
+ AttributesInfoBuilder info_builder;
+ AttributesInfo info{info_builder};
+
+ EXPECT_EQ(info.size(), 0);
+}
+
+TEST(attributes_info, AddSameNameTwice)
+{
+ AttributesInfoBuilder info_builder;
+ info_builder.add<int>("A", 4);
+ info_builder.add<int>("A", 5);
+ AttributesInfo info{info_builder};
+ EXPECT_EQ(info.size(), 1);
+ EXPECT_TRUE(info.has_attribute("A", CPPType::get<int>()));
+ EXPECT_FALSE(info.has_attribute("B", CPPType::get<int>()));
+ EXPECT_FALSE(info.has_attribute("A", CPPType::get<float>()));
+ EXPECT_EQ(info.default_of<int>("A"), 4);
+ EXPECT_EQ(info.name_of(0), "A");
+ EXPECT_EQ(info.index_range().start(), 0);
+ EXPECT_EQ(info.index_range().one_after_last(), 1);
+}
+
+TEST(attributes_info, BuildWithDefaultString)
+{
+ AttributesInfoBuilder info_builder;
+ info_builder.add("A", CPPType::get<std::string>());
+ AttributesInfo info{info_builder};
+ EXPECT_EQ(info.default_of<std::string>("A"), "");
+}
+
+TEST(attributes_info, BuildWithGivenDefault)
+{
+ AttributesInfoBuilder info_builder;
+ info_builder.add<std::string>("A", "hello world");
+ AttributesInfo info{info_builder};
+ const void *default_value = info.default_of("A");
+ EXPECT_EQ(*(const std::string *)default_value, "hello world");
+ EXPECT_EQ(info.type_of("A"), CPPType::get<std::string>());
+}
+
+TEST(mutable_attributes_ref, ComplexTest)
+{
+ AttributesInfoBuilder info_builder;
+ info_builder.add<float3>("Position", {0, 0, 10});
+ info_builder.add<uint>("ID", 0);
+ info_builder.add<float>("Size", 0.5f);
+ info_builder.add<std::string>("Name", "<no name>");
+ AttributesInfo info{info_builder};
+
+ int amount = 5;
+ Array<float3> positions(amount);
+ Array<uint> ids(amount, 0);
+ Array<float> sizes(amount);
+ Array<std::string> names(amount);
+
+ Array<void *> buffers = {positions.data(), ids.data(), sizes.data(), names.data()};
+ MutableAttributesRef attributes{info, buffers, IndexRange(1, 3)};
+ EXPECT_EQ(attributes.size(), 3);
+ EXPECT_EQ(attributes.info().size(), 4);
+ EXPECT_EQ(attributes.get("Position").data(), positions.data() + 1);
+ EXPECT_EQ(attributes.get("ID").data(), ids.data() + 1);
+ EXPECT_EQ(attributes.get("Size").data(), sizes.data() + 1);
+ EXPECT_EQ(attributes.get("Name").data(), names.data() + 1);
+
+ EXPECT_EQ(attributes.get("ID").size(), 3);
+ EXPECT_EQ(attributes.get<uint>("ID").size(), 3);
+
+ EXPECT_EQ(ids[2], 0);
+ MutableSpan<uint> ids_span = attributes.get<uint>("ID");
+ ids_span[1] = 42;
+ EXPECT_EQ(ids[2], 42);
+
+ EXPECT_FALSE(attributes.try_get<int>("not existant").has_value());
+ EXPECT_FALSE(attributes.try_get<int>("Position").has_value());
+ EXPECT_TRUE(attributes.try_get<float3>("Position").has_value());
+ EXPECT_FALSE(attributes.try_get("not existant", CPPType::get<int>()).has_value());
+ EXPECT_FALSE(attributes.try_get("Position", CPPType::get<int>()).has_value());
+ EXPECT_TRUE(attributes.try_get("Position", CPPType::get<float3>()).has_value());
+
+ MutableAttributesRef sliced = attributes.slice(IndexRange(1, 2));
+ EXPECT_EQ(sliced.size(), 2);
+ sliced.get<uint>("ID")[0] = 100;
+ EXPECT_EQ(ids[2], 100);
+}
+
+} // namespace blender::fn::tests
diff --git a/source/blender/functions/tests/FN_cpp_type_test.cc b/source/blender/functions/tests/FN_cpp_type_test.cc
new file mode 100644
index 00000000000..29368b251cc
--- /dev/null
+++ b/source/blender/functions/tests/FN_cpp_type_test.cc
@@ -0,0 +1,325 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include "FN_cpp_type.hh"
+
+namespace blender::fn::tests {
+
+static const int default_constructed_value = 1;
+static const int copy_constructed_value = 2;
+static const int move_constructed_value = 3;
+static const int copy_constructed_from_value = 4;
+static const int move_constructed_from_value = 5;
+static const int copy_assigned_value = 6;
+static const int copy_assigned_from_value = 7;
+static const int move_assigned_value = 8;
+static const int move_assigned_from_value = 9;
+static const int destructed_value = 10;
+
+struct TestType {
+ mutable volatile int value;
+
+ TestType()
+ {
+ value = default_constructed_value;
+ }
+
+ ~TestType()
+ {
+ value = destructed_value;
+ }
+
+ TestType(const TestType &other)
+ {
+ value = copy_constructed_value;
+ other.value = copy_constructed_from_value;
+ }
+
+ TestType(TestType &&other) noexcept
+ {
+ value = move_constructed_value;
+ other.value = move_constructed_from_value;
+ }
+
+ TestType &operator=(const TestType &other)
+ {
+ value = copy_assigned_value;
+ other.value = copy_assigned_from_value;
+ return *this;
+ }
+
+ TestType &operator=(TestType &&other) noexcept
+ {
+ value = move_assigned_value;
+ other.value = move_assigned_from_value;
+ return *this;
+ }
+
+ friend std::ostream &operator<<(std::ostream &stream, const TestType &value)
+ {
+ stream << value.value;
+ return stream;
+ }
+
+ friend bool operator==(const TestType &UNUSED(a), const TestType &UNUSED(b))
+ {
+ return false;
+ }
+
+ uint64_t hash() const
+ {
+ return 0;
+ }
+};
+
+} // namespace blender::fn::tests
+
+MAKE_CPP_TYPE(TestType, blender::fn::tests::TestType)
+
+namespace blender::fn::tests {
+
+const CPPType &CPPType_TestType = CPPType::get<TestType>();
+
+TEST(cpp_type, Size)
+{
+ EXPECT_EQ(CPPType_TestType.size(), sizeof(TestType));
+}
+
+TEST(cpp_type, Alignment)
+{
+ EXPECT_EQ(CPPType_TestType.alignment(), alignof(TestType));
+}
+
+TEST(cpp_type, Is)
+{
+ EXPECT_TRUE(CPPType_TestType.is<TestType>());
+ EXPECT_FALSE(CPPType_TestType.is<int>());
+}
+
+TEST(cpp_type, DefaultConstruction)
+{
+ int buffer[10] = {0};
+ CPPType_TestType.construct_default((void *)buffer);
+ EXPECT_EQ(buffer[0], default_constructed_value);
+ EXPECT_EQ(buffer[1], 0);
+ CPPType_TestType.construct_default_n((void *)buffer, 3);
+ EXPECT_EQ(buffer[0], default_constructed_value);
+ EXPECT_EQ(buffer[1], default_constructed_value);
+ EXPECT_EQ(buffer[2], default_constructed_value);
+ EXPECT_EQ(buffer[3], 0);
+ CPPType_TestType.construct_default_indices((void *)buffer, {2, 5, 7});
+ EXPECT_EQ(buffer[2], default_constructed_value);
+ EXPECT_EQ(buffer[4], 0);
+ EXPECT_EQ(buffer[5], default_constructed_value);
+ EXPECT_EQ(buffer[6], 0);
+ EXPECT_EQ(buffer[7], default_constructed_value);
+ EXPECT_EQ(buffer[8], 0);
+}
+
+TEST(cpp_type, Destruct)
+{
+ int buffer[10] = {0};
+ CPPType_TestType.destruct((void *)buffer);
+ EXPECT_EQ(buffer[0], destructed_value);
+ EXPECT_EQ(buffer[1], 0);
+ CPPType_TestType.destruct_n((void *)buffer, 3);
+ EXPECT_EQ(buffer[0], destructed_value);
+ EXPECT_EQ(buffer[1], destructed_value);
+ EXPECT_EQ(buffer[2], destructed_value);
+ EXPECT_EQ(buffer[3], 0);
+ CPPType_TestType.destruct_indices((void *)buffer, {2, 5, 7});
+ EXPECT_EQ(buffer[2], destructed_value);
+ EXPECT_EQ(buffer[4], 0);
+ EXPECT_EQ(buffer[5], destructed_value);
+ EXPECT_EQ(buffer[6], 0);
+ EXPECT_EQ(buffer[7], destructed_value);
+ EXPECT_EQ(buffer[8], 0);
+}
+
+TEST(cpp_type, CopyToUninitialized)
+{
+ int buffer1[10] = {0};
+ int buffer2[10] = {0};
+ CPPType_TestType.copy_to_uninitialized((void *)buffer1, (void *)buffer2);
+ EXPECT_EQ(buffer1[0], copy_constructed_from_value);
+ EXPECT_EQ(buffer2[0], copy_constructed_value);
+ CPPType_TestType.copy_to_uninitialized_n((void *)buffer1, (void *)buffer2, 3);
+ EXPECT_EQ(buffer1[0], copy_constructed_from_value);
+ EXPECT_EQ(buffer2[0], copy_constructed_value);
+ EXPECT_EQ(buffer1[1], copy_constructed_from_value);
+ EXPECT_EQ(buffer2[1], copy_constructed_value);
+ EXPECT_EQ(buffer1[2], copy_constructed_from_value);
+ EXPECT_EQ(buffer2[2], copy_constructed_value);
+ EXPECT_EQ(buffer1[3], 0);
+ EXPECT_EQ(buffer2[3], 0);
+ CPPType_TestType.copy_to_uninitialized_indices((void *)buffer1, (void *)buffer2, {2, 5, 7});
+ EXPECT_EQ(buffer1[2], copy_constructed_from_value);
+ EXPECT_EQ(buffer2[2], copy_constructed_value);
+ EXPECT_EQ(buffer1[4], 0);
+ EXPECT_EQ(buffer2[4], 0);
+ EXPECT_EQ(buffer1[5], copy_constructed_from_value);
+ EXPECT_EQ(buffer2[5], copy_constructed_value);
+ EXPECT_EQ(buffer1[6], 0);
+ EXPECT_EQ(buffer2[6], 0);
+ EXPECT_EQ(buffer1[7], copy_constructed_from_value);
+ EXPECT_EQ(buffer2[7], copy_constructed_value);
+ EXPECT_EQ(buffer1[8], 0);
+ EXPECT_EQ(buffer2[8], 0);
+}
+
+TEST(cpp_type, CopyToInitialized)
+{
+ int buffer1[10] = {0};
+ int buffer2[10] = {0};
+ CPPType_TestType.copy_to_initialized((void *)buffer1, (void *)buffer2);
+ EXPECT_EQ(buffer1[0], copy_assigned_from_value);
+ EXPECT_EQ(buffer2[0], copy_assigned_value);
+ CPPType_TestType.copy_to_initialized_n((void *)buffer1, (void *)buffer2, 3);
+ EXPECT_EQ(buffer1[0], copy_assigned_from_value);
+ EXPECT_EQ(buffer2[0], copy_assigned_value);
+ EXPECT_EQ(buffer1[1], copy_assigned_from_value);
+ EXPECT_EQ(buffer2[1], copy_assigned_value);
+ EXPECT_EQ(buffer1[2], copy_assigned_from_value);
+ EXPECT_EQ(buffer2[2], copy_assigned_value);
+ EXPECT_EQ(buffer1[3], 0);
+ EXPECT_EQ(buffer2[3], 0);
+ CPPType_TestType.copy_to_initialized_indices((void *)buffer1, (void *)buffer2, {2, 5, 7});
+ EXPECT_EQ(buffer1[2], copy_assigned_from_value);
+ EXPECT_EQ(buffer2[2], copy_assigned_value);
+ EXPECT_EQ(buffer1[4], 0);
+ EXPECT_EQ(buffer2[4], 0);
+ EXPECT_EQ(buffer1[5], copy_assigned_from_value);
+ EXPECT_EQ(buffer2[5], copy_assigned_value);
+ EXPECT_EQ(buffer1[6], 0);
+ EXPECT_EQ(buffer2[6], 0);
+ EXPECT_EQ(buffer1[7], copy_assigned_from_value);
+ EXPECT_EQ(buffer2[7], copy_assigned_value);
+ EXPECT_EQ(buffer1[8], 0);
+ EXPECT_EQ(buffer2[8], 0);
+}
+
+TEST(cpp_type, RelocateToUninitialized)
+{
+ int buffer1[10] = {0};
+ int buffer2[10] = {0};
+ CPPType_TestType.relocate_to_uninitialized((void *)buffer1, (void *)buffer2);
+ EXPECT_EQ(buffer1[0], destructed_value);
+ EXPECT_EQ(buffer2[0], move_constructed_value);
+ CPPType_TestType.relocate_to_uninitialized_n((void *)buffer1, (void *)buffer2, 3);
+ EXPECT_EQ(buffer1[0], destructed_value);
+ EXPECT_EQ(buffer2[0], move_constructed_value);
+ EXPECT_EQ(buffer1[1], destructed_value);
+ EXPECT_EQ(buffer2[1], move_constructed_value);
+ EXPECT_EQ(buffer1[2], destructed_value);
+ EXPECT_EQ(buffer2[2], move_constructed_value);
+ EXPECT_EQ(buffer1[3], 0);
+ EXPECT_EQ(buffer2[3], 0);
+ CPPType_TestType.relocate_to_uninitialized_indices((void *)buffer1, (void *)buffer2, {2, 5, 7});
+ EXPECT_EQ(buffer1[2], destructed_value);
+ EXPECT_EQ(buffer2[2], move_constructed_value);
+ EXPECT_EQ(buffer1[4], 0);
+ EXPECT_EQ(buffer2[4], 0);
+ EXPECT_EQ(buffer1[5], destructed_value);
+ EXPECT_EQ(buffer2[5], move_constructed_value);
+ EXPECT_EQ(buffer1[6], 0);
+ EXPECT_EQ(buffer2[6], 0);
+ EXPECT_EQ(buffer1[7], destructed_value);
+ EXPECT_EQ(buffer2[7], move_constructed_value);
+ EXPECT_EQ(buffer1[8], 0);
+ EXPECT_EQ(buffer2[8], 0);
+}
+
+TEST(cpp_type, RelocateToInitialized)
+{
+ int buffer1[10] = {0};
+ int buffer2[10] = {0};
+ CPPType_TestType.relocate_to_initialized((void *)buffer1, (void *)buffer2);
+ EXPECT_EQ(buffer1[0], destructed_value);
+ EXPECT_EQ(buffer2[0], move_assigned_value);
+ CPPType_TestType.relocate_to_initialized_n((void *)buffer1, (void *)buffer2, 3);
+ EXPECT_EQ(buffer1[0], destructed_value);
+ EXPECT_EQ(buffer2[0], move_assigned_value);
+ EXPECT_EQ(buffer1[1], destructed_value);
+ EXPECT_EQ(buffer2[1], move_assigned_value);
+ EXPECT_EQ(buffer1[2], destructed_value);
+ EXPECT_EQ(buffer2[2], move_assigned_value);
+ EXPECT_EQ(buffer1[3], 0);
+ EXPECT_EQ(buffer2[3], 0);
+ CPPType_TestType.relocate_to_initialized_indices((void *)buffer1, (void *)buffer2, {2, 5, 7});
+ EXPECT_EQ(buffer1[2], destructed_value);
+ EXPECT_EQ(buffer2[2], move_assigned_value);
+ EXPECT_EQ(buffer1[4], 0);
+ EXPECT_EQ(buffer2[4], 0);
+ EXPECT_EQ(buffer1[5], destructed_value);
+ EXPECT_EQ(buffer2[5], move_assigned_value);
+ EXPECT_EQ(buffer1[6], 0);
+ EXPECT_EQ(buffer2[6], 0);
+ EXPECT_EQ(buffer1[7], destructed_value);
+ EXPECT_EQ(buffer2[7], move_assigned_value);
+ EXPECT_EQ(buffer1[8], 0);
+ EXPECT_EQ(buffer2[8], 0);
+}
+
+TEST(cpp_type, FillInitialized)
+{
+ int buffer1 = 0;
+ int buffer2[10] = {0};
+ CPPType_TestType.fill_initialized((void *)&buffer1, (void *)buffer2, 3);
+ EXPECT_EQ(buffer1, copy_assigned_from_value);
+ EXPECT_EQ(buffer2[0], copy_assigned_value);
+ EXPECT_EQ(buffer2[1], copy_assigned_value);
+ EXPECT_EQ(buffer2[2], copy_assigned_value);
+ EXPECT_EQ(buffer2[3], 0);
+
+ buffer1 = 0;
+ CPPType_TestType.fill_initialized_indices((void *)&buffer1, (void *)buffer2, {1, 6, 8});
+ EXPECT_EQ(buffer1, copy_assigned_from_value);
+ EXPECT_EQ(buffer2[0], copy_assigned_value);
+ EXPECT_EQ(buffer2[1], copy_assigned_value);
+ EXPECT_EQ(buffer2[2], copy_assigned_value);
+ EXPECT_EQ(buffer2[3], 0);
+ EXPECT_EQ(buffer2[4], 0);
+ EXPECT_EQ(buffer2[5], 0);
+ EXPECT_EQ(buffer2[6], copy_assigned_value);
+ EXPECT_EQ(buffer2[7], 0);
+ EXPECT_EQ(buffer2[8], copy_assigned_value);
+ EXPECT_EQ(buffer2[9], 0);
+}
+
+TEST(cpp_type, FillUninitialized)
+{
+ int buffer1 = 0;
+ int buffer2[10] = {0};
+ CPPType_TestType.fill_uninitialized((void *)&buffer1, (void *)buffer2, 3);
+ EXPECT_EQ(buffer1, copy_constructed_from_value);
+ EXPECT_EQ(buffer2[0], copy_constructed_value);
+ EXPECT_EQ(buffer2[1], copy_constructed_value);
+ EXPECT_EQ(buffer2[2], copy_constructed_value);
+ EXPECT_EQ(buffer2[3], 0);
+
+ buffer1 = 0;
+ CPPType_TestType.fill_uninitialized_indices((void *)&buffer1, (void *)buffer2, {1, 6, 8});
+ EXPECT_EQ(buffer1, copy_constructed_from_value);
+ EXPECT_EQ(buffer2[0], copy_constructed_value);
+ EXPECT_EQ(buffer2[1], copy_constructed_value);
+ EXPECT_EQ(buffer2[2], copy_constructed_value);
+ EXPECT_EQ(buffer2[3], 0);
+ EXPECT_EQ(buffer2[4], 0);
+ EXPECT_EQ(buffer2[5], 0);
+ EXPECT_EQ(buffer2[6], copy_constructed_value);
+ EXPECT_EQ(buffer2[7], 0);
+ EXPECT_EQ(buffer2[8], copy_constructed_value);
+ EXPECT_EQ(buffer2[9], 0);
+}
+
+TEST(cpp_type, DebugPrint)
+{
+ int value = 42;
+ std::stringstream ss;
+ CPPType::get<int32_t>().debug_print((void *)&value, ss);
+ std::string text = ss.str();
+ EXPECT_EQ(text, "42");
+}
+
+} // namespace blender::fn::tests
diff --git a/source/blender/functions/tests/FN_generic_vector_array_test.cc b/source/blender/functions/tests/FN_generic_vector_array_test.cc
new file mode 100644
index 00000000000..77ec05f12dc
--- /dev/null
+++ b/source/blender/functions/tests/FN_generic_vector_array_test.cc
@@ -0,0 +1,101 @@
+/* Apache License, Version 2.0 */
+
+#include "FN_generic_vector_array.hh"
+
+#include "testing/testing.h"
+
+namespace blender::fn::tests {
+
+TEST(generic_vector_array, Constructor)
+{
+ GVectorArray vectors{CPPType::get<int32_t>(), 3};
+ EXPECT_EQ(vectors.size(), 3);
+ EXPECT_EQ(vectors.lengths().size(), 3);
+ EXPECT_EQ(vectors.starts().size(), 3);
+ EXPECT_EQ(vectors.lengths()[0], 0);
+ EXPECT_EQ(vectors.lengths()[1], 0);
+ EXPECT_EQ(vectors.lengths()[2], 0);
+ EXPECT_EQ(vectors.type(), CPPType::get<int32_t>());
+}
+
+TEST(generic_vector_array, Append)
+{
+ GVectorArray vectors{CPPType::get<std::string>(), 3};
+ std::string value = "hello";
+ vectors.append(0, &value);
+ value = "world";
+ vectors.append(0, &value);
+ vectors.append(2, &value);
+
+ EXPECT_EQ(vectors.lengths()[0], 2);
+ EXPECT_EQ(vectors.lengths()[1], 0);
+ EXPECT_EQ(vectors.lengths()[2], 1);
+ EXPECT_EQ(vectors[0].size(), 2);
+ EXPECT_EQ(vectors[0].typed<std::string>()[0], "hello");
+ EXPECT_EQ(vectors[0].typed<std::string>()[1], "world");
+ EXPECT_EQ(vectors[2].typed<std::string>()[0], "world");
+}
+
+TEST(generic_vector_array, AsArraySpan)
+{
+ GVectorArray vectors{CPPType::get<int32_t>(), 3};
+ int value = 3;
+ vectors.append(0, &value);
+ vectors.append(0, &value);
+ value = 5;
+ vectors.append(2, &value);
+ vectors.append(2, &value);
+ vectors.append(2, &value);
+
+ GVArraySpan span = vectors;
+ EXPECT_EQ(span.type(), CPPType::get<int32_t>());
+ EXPECT_EQ(span.size(), 3);
+ EXPECT_EQ(span[0].size(), 2);
+ EXPECT_EQ(span[1].size(), 0);
+ EXPECT_EQ(span[2].size(), 3);
+ EXPECT_EQ(span[0].typed<int>()[1], 3);
+ EXPECT_EQ(span[2].typed<int>()[0], 5);
+}
+
+TEST(generic_vector_array, TypedRef)
+{
+ GVectorArray vectors{CPPType::get<int32_t>(), 4};
+ GVectorArrayRef<int> ref = vectors.typed<int>();
+ ref.append(0, 2);
+ ref.append(0, 6);
+ ref.append(0, 7);
+ ref.append(2, 1);
+ ref.append(2, 1);
+ ref.append(3, 5);
+ ref.append(3, 6);
+
+ EXPECT_EQ(ref[0].size(), 3);
+ EXPECT_EQ(vectors[0].size(), 3);
+ EXPECT_EQ(ref[0][0], 2);
+ EXPECT_EQ(ref[0][1], 6);
+ EXPECT_EQ(ref[0][2], 7);
+ EXPECT_EQ(ref[1].size(), 0);
+ EXPECT_EQ(ref[2][0], 1);
+ EXPECT_EQ(ref[2][1], 1);
+ EXPECT_EQ(ref[3][0], 5);
+ EXPECT_EQ(ref[3][1], 6);
+}
+
+TEST(generic_vector_array, Extend)
+{
+ GVectorArray vectors{CPPType::get<int32_t>(), 3};
+ GVectorArrayRef<int> ref = vectors;
+
+ ref.extend(1, {5, 6, 7});
+ ref.extend(0, {3});
+
+ EXPECT_EQ(vectors[0].size(), 1);
+ EXPECT_EQ(vectors[1].size(), 3);
+ EXPECT_EQ(vectors[2].size(), 0);
+ EXPECT_EQ(ref[1][0], 5);
+ EXPECT_EQ(ref[1][1], 6);
+ EXPECT_EQ(ref[1][2], 7);
+ EXPECT_EQ(ref[0][0], 3);
+}
+
+} // namespace blender::fn::tests
diff --git a/source/blender/functions/tests/FN_multi_function_network_test.cc b/source/blender/functions/tests/FN_multi_function_network_test.cc
new file mode 100644
index 00000000000..53290e9170c
--- /dev/null
+++ b/source/blender/functions/tests/FN_multi_function_network_test.cc
@@ -0,0 +1,253 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include "FN_multi_function_builder.hh"
+#include "FN_multi_function_network.hh"
+#include "FN_multi_function_network_evaluation.hh"
+
+namespace blender::fn::tests {
+
+TEST(multi_function_network, Test1)
+{
+ CustomMF_SI_SO<int, int> add_10_fn("add 10", [](int value) { return value + 10; });
+ CustomMF_SI_SI_SO<int, int, int> multiply_fn("multiply", [](int a, int b) { return a * b; });
+
+ MFNetwork network;
+
+ MFNode &node1 = network.add_function(add_10_fn);
+ MFNode &node2 = network.add_function(multiply_fn);
+ MFOutputSocket &input_socket = network.add_input("Input", MFDataType::ForSingle<int>());
+ MFInputSocket &output_socket = network.add_output("Output", MFDataType::ForSingle<int>());
+ network.add_link(node1.output(0), node2.input(0));
+ network.add_link(node1.output(0), node2.input(1));
+ network.add_link(node2.output(0), output_socket);
+ network.add_link(input_socket, node1.input(0));
+
+ MFNetworkEvaluator network_fn{{&input_socket}, {&output_socket}};
+
+ {
+ Array<int> values = {4, 6, 1, 2, 0};
+ Array<int> results(values.size(), 0);
+
+ MFParamsBuilder params(network_fn, values.size());
+ params.add_readonly_single_input(values.as_span());
+ params.add_uninitialized_single_output(results.as_mutable_span());
+
+ MFContextBuilder context;
+
+ network_fn.call({0, 2, 3, 4}, params, context);
+
+ EXPECT_EQ(results[0], 14 * 14);
+ EXPECT_EQ(results[1], 0);
+ EXPECT_EQ(results[2], 11 * 11);
+ EXPECT_EQ(results[3], 12 * 12);
+ EXPECT_EQ(results[4], 10 * 10);
+ }
+ {
+ int value = 3;
+ Array<int> results(5, 0);
+
+ MFParamsBuilder params(network_fn, results.size());
+ params.add_readonly_single_input(&value);
+ params.add_uninitialized_single_output(results.as_mutable_span());
+
+ MFContextBuilder context;
+
+ network_fn.call({1, 2, 4}, params, context);
+
+ EXPECT_EQ(results[0], 0);
+ EXPECT_EQ(results[1], 13 * 13);
+ EXPECT_EQ(results[2], 13 * 13);
+ EXPECT_EQ(results[3], 0);
+ EXPECT_EQ(results[4], 13 * 13);
+ }
+}
+
+class ConcatVectorsFunction : public MultiFunction {
+ public:
+ ConcatVectorsFunction()
+ {
+ MFSignatureBuilder signature = this->get_builder("Concat Vectors");
+ signature.vector_mutable<int>("A");
+ signature.vector_input<int>("B");
+ }
+
+ void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
+ {
+ GVectorArrayRef<int> a = params.vector_mutable<int>(0);
+ VArraySpan<int> b = params.readonly_vector_input<int>(1);
+
+ for (int64_t i : mask) {
+ a.extend(i, b[i]);
+ }
+ }
+};
+
+class AppendFunction : public MultiFunction {
+ public:
+ AppendFunction()
+ {
+ MFSignatureBuilder signature = this->get_builder("Append");
+ signature.vector_mutable<int>("Vector");
+ signature.single_input<int>("Value");
+ }
+
+ void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
+ {
+ GVectorArrayRef<int> vectors = params.vector_mutable<int>(0);
+ VSpan<int> values = params.readonly_single_input<int>(1);
+
+ for (int64_t i : mask) {
+ vectors.append(i, values[i]);
+ }
+ }
+};
+
+class SumVectorFunction : public MultiFunction {
+ public:
+ SumVectorFunction()
+ {
+ MFSignatureBuilder signature = this->get_builder("Sum Vector");
+ signature.vector_input<int>("Vector");
+ signature.single_output<int>("Sum");
+ }
+
+ void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
+ {
+ VArraySpan<int> vectors = params.readonly_vector_input<int>(0);
+ MutableSpan<int> sums = params.uninitialized_single_output<int>(1);
+
+ for (int64_t i : mask) {
+ int sum = 0;
+ VSpan<int> vector = vectors[i];
+ for (int j = 0; j < vector.size(); j++) {
+ sum += vector[j];
+ }
+ sums[i] = sum;
+ }
+ }
+};
+
+class CreateRangeFunction : public MultiFunction {
+ public:
+ CreateRangeFunction()
+ {
+ MFSignatureBuilder builder = this->get_builder("Create Range");
+ builder.single_input<int>("Size");
+ builder.vector_output<int>("Range");
+ }
+
+ void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
+ {
+ VSpan<int> sizes = params.readonly_single_input<int>(0, "Size");
+ GVectorArrayRef<int> ranges = params.vector_output<int>(1, "Range");
+
+ for (int64_t i : mask) {
+ int size = sizes[i];
+ for (int j : IndexRange(size)) {
+ ranges.append(i, j);
+ }
+ }
+ }
+};
+
+TEST(multi_function_network, Test2)
+{
+ CustomMF_SI_SO<int, int> add_3_fn("add 3", [](int value) { return value + 3; });
+
+ ConcatVectorsFunction concat_vectors_fn;
+ AppendFunction append_fn;
+ SumVectorFunction sum_fn;
+ CreateRangeFunction create_range_fn;
+
+ MFNetwork network;
+
+ MFOutputSocket &input1 = network.add_input("Input 1", MFDataType::ForVector<int>());
+ MFOutputSocket &input2 = network.add_input("Input 2", MFDataType::ForSingle<int>());
+ MFInputSocket &output1 = network.add_output("Output 1", MFDataType::ForVector<int>());
+ MFInputSocket &output2 = network.add_output("Output 2", MFDataType::ForSingle<int>());
+
+ MFNode &node1 = network.add_function(add_3_fn);
+ MFNode &node2 = network.add_function(create_range_fn);
+ MFNode &node3 = network.add_function(concat_vectors_fn);
+ MFNode &node4 = network.add_function(sum_fn);
+ MFNode &node5 = network.add_function(append_fn);
+ MFNode &node6 = network.add_function(sum_fn);
+
+ network.add_link(input2, node1.input(0));
+ network.add_link(node1.output(0), node2.input(0));
+ network.add_link(node2.output(0), node3.input(1));
+ network.add_link(input1, node3.input(0));
+ network.add_link(input1, node4.input(0));
+ network.add_link(node4.output(0), node5.input(1));
+ network.add_link(node3.output(0), node5.input(0));
+ network.add_link(node5.output(0), node6.input(0));
+ network.add_link(node3.output(0), output1);
+ network.add_link(node6.output(0), output2);
+
+ // std::cout << network.to_dot() << "\n\n";
+
+ MFNetworkEvaluator network_fn{{&input1, &input2}, {&output1, &output2}};
+
+ {
+ Array<int> input_value_1 = {3, 6};
+ int input_value_2 = 4;
+
+ GVectorArray output_value_1(CPPType::get<int32_t>(), 5);
+ Array<int> output_value_2(5, -1);
+
+ MFParamsBuilder params(network_fn, 5);
+ params.add_readonly_vector_input(GVArraySpan(input_value_1.as_span(), 5));
+ params.add_readonly_single_input(&input_value_2);
+ params.add_vector_output(output_value_1);
+ params.add_uninitialized_single_output(output_value_2.as_mutable_span());
+
+ MFContextBuilder context;
+
+ network_fn.call({1, 2, 4}, params, context);
+
+ EXPECT_EQ(output_value_1[0].size(), 0);
+ EXPECT_EQ(output_value_1[1].size(), 9);
+ EXPECT_EQ(output_value_1[2].size(), 9);
+ EXPECT_EQ(output_value_1[3].size(), 0);
+ EXPECT_EQ(output_value_1[4].size(), 9);
+
+ EXPECT_EQ(output_value_2[0], -1);
+ EXPECT_EQ(output_value_2[1], 39);
+ EXPECT_EQ(output_value_2[2], 39);
+ EXPECT_EQ(output_value_2[3], -1);
+ EXPECT_EQ(output_value_2[4], 39);
+ }
+ {
+ GVectorArray input_value_1(CPPType::get<int32_t>(), 3);
+ GVectorArrayRef<int> input_value_ref_1 = input_value_1;
+ input_value_ref_1.extend(0, {3, 4, 5});
+ input_value_ref_1.extend(1, {1, 2});
+
+ Array<int> input_value_2 = {4, 2, 3};
+
+ GVectorArray output_value_1(CPPType::get<int32_t>(), 3);
+ Array<int> output_value_2(3, -1);
+
+ MFParamsBuilder params(network_fn, 3);
+ params.add_readonly_vector_input(input_value_1);
+ params.add_readonly_single_input(input_value_2.as_span());
+ params.add_vector_output(output_value_1);
+ params.add_uninitialized_single_output(output_value_2.as_mutable_span());
+
+ MFContextBuilder context;
+
+ network_fn.call({0, 1, 2}, params, context);
+
+ EXPECT_EQ(output_value_1[0].size(), 10);
+ EXPECT_EQ(output_value_1[1].size(), 7);
+ EXPECT_EQ(output_value_1[2].size(), 6);
+
+ EXPECT_EQ(output_value_2[0], 45);
+ EXPECT_EQ(output_value_2[1], 16);
+ EXPECT_EQ(output_value_2[2], 15);
+ }
+}
+
+} // namespace blender::fn::tests
diff --git a/source/blender/functions/tests/FN_multi_function_test.cc b/source/blender/functions/tests/FN_multi_function_test.cc
new file mode 100644
index 00000000000..6acb6e22b01
--- /dev/null
+++ b/source/blender/functions/tests/FN_multi_function_test.cc
@@ -0,0 +1,385 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include "FN_multi_function.hh"
+#include "FN_multi_function_builder.hh"
+
+namespace blender::fn::tests {
+
+class AddFunction : public MultiFunction {
+ public:
+ AddFunction()
+ {
+ MFSignatureBuilder builder = this->get_builder("Add");
+ builder.single_input<int>("A");
+ builder.single_input<int>("B");
+ builder.single_output<int>("Result");
+ }
+
+ void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
+ {
+ VSpan<int> a = params.readonly_single_input<int>(0, "A");
+ VSpan<int> b = params.readonly_single_input<int>(1, "B");
+ MutableSpan<int> result = params.uninitialized_single_output<int>(2, "Result");
+
+ for (int64_t i : mask) {
+ result[i] = a[i] + b[i];
+ }
+ }
+};
+
+TEST(multi_function, AddFunction)
+{
+ AddFunction fn;
+
+ Array<int> input1 = {4, 5, 6};
+ Array<int> input2 = {10, 20, 30};
+ Array<int> output(3, -1);
+
+ MFParamsBuilder params(fn, 3);
+ params.add_readonly_single_input(input1.as_span());
+ params.add_readonly_single_input(input2.as_span());
+ params.add_uninitialized_single_output(output.as_mutable_span());
+
+ MFContextBuilder context;
+
+ fn.call({0, 2}, params, context);
+
+ EXPECT_EQ(output[0], 14);
+ EXPECT_EQ(output[1], -1);
+ EXPECT_EQ(output[2], 36);
+}
+
+class AddPrefixFunction : public MultiFunction {
+ public:
+ AddPrefixFunction()
+ {
+ MFSignatureBuilder builder = this->get_builder("Add Prefix");
+ builder.single_input<std::string>("Prefix");
+ builder.single_mutable<std::string>("Strings");
+ }
+
+ void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
+ {
+ VSpan<std::string> prefixes = params.readonly_single_input<std::string>(0, "Prefix");
+ MutableSpan<std::string> strings = params.single_mutable<std::string>(1, "Strings");
+
+ for (int64_t i : mask) {
+ strings[i] = prefixes[i] + strings[i];
+ }
+ }
+};
+
+TEST(multi_function, AddPrefixFunction)
+{
+ AddPrefixFunction fn;
+
+ Array<std::string> strings = {
+ "Hello",
+ "World",
+ "This is a test",
+ "Another much longer string to trigger an allocation",
+ };
+
+ std::string prefix = "AB";
+
+ MFParamsBuilder params(fn, strings.size());
+ params.add_readonly_single_input(&prefix);
+ params.add_single_mutable(strings.as_mutable_span());
+
+ MFContextBuilder context;
+
+ fn.call({0, 2, 3}, params, context);
+
+ EXPECT_EQ(strings[0], "ABHello");
+ EXPECT_EQ(strings[1], "World");
+ EXPECT_EQ(strings[2], "ABThis is a test");
+ EXPECT_EQ(strings[3], "ABAnother much longer string to trigger an allocation");
+}
+
+class CreateRangeFunction : public MultiFunction {
+ public:
+ CreateRangeFunction()
+ {
+ MFSignatureBuilder builder = this->get_builder("Create Range");
+ builder.single_input<uint>("Size");
+ builder.vector_output<uint>("Range");
+ }
+
+ void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
+ {
+ VSpan<uint> sizes = params.readonly_single_input<uint>(0, "Size");
+ GVectorArrayRef<uint> ranges = params.vector_output<uint>(1, "Range");
+
+ for (int64_t i : mask) {
+ uint size = sizes[i];
+ for (uint j : IndexRange(size)) {
+ ranges.append(i, j);
+ }
+ }
+ }
+};
+
+TEST(multi_function, CreateRangeFunction)
+{
+ CreateRangeFunction fn;
+
+ GVectorArray ranges(CPPType::get<int32_t>(), 5);
+ GVectorArrayRef<uint> ranges_ref(ranges);
+ Array<uint> sizes = {3, 0, 6, 1, 4};
+
+ MFParamsBuilder params(fn, ranges.size());
+ params.add_readonly_single_input(sizes.as_span());
+ params.add_vector_output(ranges);
+
+ MFContextBuilder context;
+
+ fn.call({0, 1, 2, 3}, params, context);
+
+ EXPECT_EQ(ranges_ref[0].size(), 3);
+ EXPECT_EQ(ranges_ref[1].size(), 0);
+ EXPECT_EQ(ranges_ref[2].size(), 6);
+ EXPECT_EQ(ranges_ref[3].size(), 1);
+ EXPECT_EQ(ranges_ref[4].size(), 0);
+
+ EXPECT_EQ(ranges_ref[0][0], 0);
+ EXPECT_EQ(ranges_ref[0][1], 1);
+ EXPECT_EQ(ranges_ref[0][2], 2);
+ EXPECT_EQ(ranges_ref[2][0], 0);
+ EXPECT_EQ(ranges_ref[2][1], 1);
+}
+
+class GenericAppendFunction : public MultiFunction {
+ public:
+ GenericAppendFunction(const CPPType &type)
+ {
+ MFSignatureBuilder builder = this->get_builder("Append");
+ builder.vector_mutable("Vector", type);
+ builder.single_input("Value", type);
+ }
+
+ void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
+ {
+ GVectorArray &vectors = params.vector_mutable(0, "Vector");
+ GVSpan values = params.readonly_single_input(1, "Value");
+
+ for (int64_t i : mask) {
+ vectors.append(i, values[i]);
+ }
+ }
+};
+
+TEST(multi_function, GenericAppendFunction)
+{
+ GenericAppendFunction fn(CPPType::get<int32_t>());
+
+ GVectorArray vectors(CPPType::get<int32_t>(), 4);
+ GVectorArrayRef<int> vectors_ref(vectors);
+ vectors_ref.append(0, 1);
+ vectors_ref.append(0, 2);
+ vectors_ref.append(2, 6);
+ Array<int> values = {5, 7, 3, 1};
+
+ MFParamsBuilder params(fn, vectors.size());
+ params.add_vector_mutable(vectors);
+ params.add_readonly_single_input(values.as_span());
+
+ MFContextBuilder context;
+
+ fn.call(IndexRange(vectors.size()), params, context);
+
+ EXPECT_EQ(vectors_ref[0].size(), 3);
+ EXPECT_EQ(vectors_ref[1].size(), 1);
+ EXPECT_EQ(vectors_ref[2].size(), 2);
+ EXPECT_EQ(vectors_ref[3].size(), 1);
+
+ EXPECT_EQ(vectors_ref[0][0], 1);
+ EXPECT_EQ(vectors_ref[0][1], 2);
+ EXPECT_EQ(vectors_ref[0][2], 5);
+ EXPECT_EQ(vectors_ref[1][0], 7);
+ EXPECT_EQ(vectors_ref[2][0], 6);
+ EXPECT_EQ(vectors_ref[2][1], 3);
+ EXPECT_EQ(vectors_ref[3][0], 1);
+}
+
+TEST(multi_function, CustomMF_SI_SO)
+{
+ CustomMF_SI_SO<std::string, uint> fn("strlen",
+ [](const std::string &str) { return str.size(); });
+
+ Array<std::string> strings = {"hello", "world", "test", "another test"};
+ Array<uint> sizes(strings.size(), 0);
+
+ MFParamsBuilder params(fn, strings.size());
+ params.add_readonly_single_input(strings.as_span());
+ params.add_uninitialized_single_output(sizes.as_mutable_span());
+
+ MFContextBuilder context;
+
+ fn.call(IndexRange(strings.size()), params, context);
+
+ EXPECT_EQ(sizes[0], 5);
+ EXPECT_EQ(sizes[1], 5);
+ EXPECT_EQ(sizes[2], 4);
+ EXPECT_EQ(sizes[3], 12);
+}
+
+TEST(multi_function, CustomMF_SI_SI_SO)
+{
+ CustomMF_SI_SI_SO<int, int, int> fn("mul", [](int a, int b) { return a * b; });
+
+ Array<int> values_a = {4, 6, 8, 9};
+ int value_b = 10;
+ Array<int> outputs(values_a.size(), -1);
+
+ MFParamsBuilder params(fn, values_a.size());
+ params.add_readonly_single_input(values_a.as_span());
+ params.add_readonly_single_input(&value_b);
+ params.add_uninitialized_single_output(outputs.as_mutable_span());
+
+ MFContextBuilder context;
+
+ fn.call({0, 1, 3}, params, context);
+
+ EXPECT_EQ(outputs[0], 40);
+ EXPECT_EQ(outputs[1], 60);
+ EXPECT_EQ(outputs[2], -1);
+ EXPECT_EQ(outputs[3], 90);
+}
+
+TEST(multi_function, CustomMF_SI_SI_SI_SO)
+{
+ CustomMF_SI_SI_SI_SO<int, std::string, bool, uint> fn{
+ "custom",
+ [](int a, const std::string &b, bool c) { return (uint)((uint)a + b.size() + (uint)c); }};
+
+ Array<int> values_a = {5, 7, 3, 8};
+ Array<std::string> values_b = {"hello", "world", "another", "test"};
+ Array<bool> values_c = {true, false, false, true};
+ Array<uint> outputs(values_a.size(), 0);
+
+ MFParamsBuilder params(fn, values_a.size());
+ params.add_readonly_single_input(values_a.as_span());
+ params.add_readonly_single_input(values_b.as_span());
+ params.add_readonly_single_input(values_c.as_span());
+ params.add_uninitialized_single_output(outputs.as_mutable_span());
+
+ MFContextBuilder context;
+
+ fn.call({1, 2, 3}, params, context);
+
+ EXPECT_EQ(outputs[0], 0);
+ EXPECT_EQ(outputs[1], 12);
+ EXPECT_EQ(outputs[2], 10);
+ EXPECT_EQ(outputs[3], 13);
+}
+
+TEST(multi_function, CustomMF_SM)
+{
+ CustomMF_SM<std::string> fn("AddSuffix", [](std::string &value) { value += " test"; });
+
+ Array<std::string> values = {"a", "b", "c", "d", "e"};
+
+ MFParamsBuilder params(fn, values.size());
+ params.add_single_mutable(values.as_mutable_span());
+
+ MFContextBuilder context;
+
+ fn.call({1, 2, 3}, params, context);
+
+ EXPECT_EQ(values[0], "a");
+ EXPECT_EQ(values[1], "b test");
+ EXPECT_EQ(values[2], "c test");
+ EXPECT_EQ(values[3], "d test");
+ EXPECT_EQ(values[4], "e");
+}
+
+TEST(multi_function, CustomMF_Constant)
+{
+ CustomMF_Constant<int> fn{42};
+
+ Array<int> outputs(4, 0);
+
+ MFParamsBuilder params(fn, outputs.size());
+ params.add_uninitialized_single_output(outputs.as_mutable_span());
+
+ MFContextBuilder context;
+
+ fn.call({0, 2, 3}, params, context);
+
+ EXPECT_EQ(outputs[0], 42);
+ EXPECT_EQ(outputs[1], 0);
+ EXPECT_EQ(outputs[2], 42);
+ EXPECT_EQ(outputs[3], 42);
+}
+
+TEST(multi_function, CustomMF_GenericConstant)
+{
+ int value = 42;
+ CustomMF_GenericConstant fn{CPPType::get<int32_t>(), (const void *)&value};
+ EXPECT_EQ(fn.param_name(0), "42");
+
+ Array<int> outputs(4, 0);
+
+ MFParamsBuilder params(fn, outputs.size());
+ params.add_uninitialized_single_output(outputs.as_mutable_span());
+
+ MFContextBuilder context;
+
+ fn.call({0, 1, 2}, params, context);
+
+ EXPECT_EQ(outputs[0], 42);
+ EXPECT_EQ(outputs[1], 42);
+ EXPECT_EQ(outputs[2], 42);
+ EXPECT_EQ(outputs[3], 0);
+}
+
+TEST(multi_function, CustomMF_GenericConstantArray)
+{
+ std::array<int, 4> values = {3, 4, 5, 6};
+ CustomMF_GenericConstantArray fn{GSpan(Span(values))};
+ EXPECT_EQ(fn.param_name(0), "[3, 4, 5, 6, ]");
+
+ GVectorArray g_vector_array{CPPType::get<int32_t>(), 4};
+ GVectorArrayRef<int> vector_array = g_vector_array;
+
+ MFParamsBuilder params(fn, g_vector_array.size());
+ params.add_vector_output(g_vector_array);
+
+ MFContextBuilder context;
+
+ fn.call({1, 2, 3}, params, context);
+
+ EXPECT_EQ(vector_array[0].size(), 0);
+ EXPECT_EQ(vector_array[1].size(), 4);
+ EXPECT_EQ(vector_array[2].size(), 4);
+ EXPECT_EQ(vector_array[3].size(), 4);
+ for (int i = 1; i < 4; i++) {
+ EXPECT_EQ(vector_array[i][0], 3);
+ EXPECT_EQ(vector_array[i][1], 4);
+ EXPECT_EQ(vector_array[i][2], 5);
+ EXPECT_EQ(vector_array[i][3], 6);
+ }
+}
+
+TEST(multi_function, CustomMF_Convert)
+{
+ CustomMF_Convert<float, int> fn;
+
+ Array<float> inputs = {5.4f, 7.1f, 9.0f};
+ Array<int> outputs(inputs.size(), 0);
+
+ MFParamsBuilder params(fn, inputs.size());
+ params.add_readonly_single_input(inputs.as_span());
+ params.add_uninitialized_single_output(outputs.as_mutable_span());
+
+ MFContextBuilder context;
+ fn.call({0, 2}, params, context);
+
+ EXPECT_EQ(outputs[0], 5);
+ EXPECT_EQ(outputs[1], 0);
+ EXPECT_EQ(outputs[2], 9);
+}
+
+} // namespace blender::fn::tests
diff --git a/source/blender/functions/tests/FN_spans_test.cc b/source/blender/functions/tests/FN_spans_test.cc
new file mode 100644
index 00000000000..527c91d7846
--- /dev/null
+++ b/source/blender/functions/tests/FN_spans_test.cc
@@ -0,0 +1,214 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include "FN_spans.hh"
+
+namespace blender::fn::tests {
+
+TEST(generic_span, TypeConstructor)
+{
+ GSpan span(CPPType::get<float>());
+ EXPECT_EQ(span.size(), 0);
+ EXPECT_EQ(span.typed<float>().size(), 0);
+ EXPECT_TRUE(span.is_empty());
+}
+
+TEST(generic_span, BufferAndSizeConstructor)
+{
+ int values[4] = {6, 7, 3, 2};
+ void *buffer = (void *)values;
+ GSpan span(CPPType::get<int32_t>(), buffer, 4);
+ EXPECT_EQ(span.size(), 4);
+ EXPECT_FALSE(span.is_empty());
+ EXPECT_EQ(span.typed<int>().size(), 4);
+ EXPECT_EQ(span[0], &values[0]);
+ EXPECT_EQ(span[1], &values[1]);
+ EXPECT_EQ(span[2], &values[2]);
+ EXPECT_EQ(span[3], &values[3]);
+}
+
+TEST(generic_mutable_span, TypeConstructor)
+{
+ GMutableSpan span(CPPType::get<int32_t>());
+ EXPECT_EQ(span.size(), 0);
+ EXPECT_TRUE(span.is_empty());
+}
+
+TEST(generic_mutable_span, BufferAndSizeConstructor)
+{
+ int values[4] = {4, 7, 3, 5};
+ void *buffer = (void *)values;
+ GMutableSpan span(CPPType::get<int32_t>(), buffer, 4);
+ EXPECT_EQ(span.size(), 4);
+ EXPECT_FALSE(span.is_empty());
+ EXPECT_EQ(span.typed<int>().size(), 4);
+ EXPECT_EQ(values[2], 3);
+ *(int *)span[2] = 10;
+ EXPECT_EQ(values[2], 10);
+ span.typed<int>()[2] = 20;
+ EXPECT_EQ(values[2], 20);
+}
+
+TEST(virtual_span, EmptyConstructor)
+{
+ VSpan<int> span;
+ EXPECT_EQ(span.size(), 0);
+ EXPECT_TRUE(span.is_empty());
+ EXPECT_FALSE(span.is_single_element());
+
+ GVSpan converted(span);
+ EXPECT_EQ(converted.type(), CPPType::get<int>());
+ EXPECT_EQ(converted.size(), 0);
+}
+
+TEST(virtual_span, SpanConstructor)
+{
+ std::array<int, 5> values = {7, 3, 8, 6, 4};
+ Span<int> span = values;
+ VSpan<int> virtual_span = span;
+ EXPECT_EQ(virtual_span.size(), 5);
+ EXPECT_FALSE(virtual_span.is_empty());
+ EXPECT_EQ(virtual_span[0], 7);
+ EXPECT_EQ(virtual_span[2], 8);
+ EXPECT_EQ(virtual_span[3], 6);
+ EXPECT_FALSE(virtual_span.is_single_element());
+
+ GVSpan converted(span);
+ EXPECT_EQ(converted.type(), CPPType::get<int>());
+ EXPECT_EQ(converted.size(), 5);
+}
+
+TEST(virtual_span, PointerSpanConstructor)
+{
+ int x0 = 3;
+ int x1 = 6;
+ int x2 = 7;
+ std::array<const int *, 3> pointers = {&x0, &x2, &x1};
+ VSpan<int> span = Span<const int *>(pointers);
+ EXPECT_EQ(span.size(), 3);
+ EXPECT_FALSE(span.is_empty());
+ EXPECT_EQ(span[0], 3);
+ EXPECT_EQ(span[1], 7);
+ EXPECT_EQ(span[2], 6);
+ EXPECT_EQ(&span[1], &x2);
+ EXPECT_FALSE(span.is_single_element());
+
+ GVSpan converted(span);
+ EXPECT_EQ(converted.type(), CPPType::get<int>());
+ EXPECT_EQ(converted.size(), 3);
+ EXPECT_EQ(converted[0], &x0);
+ EXPECT_EQ(converted[1], &x2);
+ EXPECT_EQ(converted[2], &x1);
+}
+
+TEST(virtual_span, SingleConstructor)
+{
+ int value = 5;
+ VSpan<int> span = VSpan<int>::FromSingle(&value, 3);
+ EXPECT_EQ(span.size(), 3);
+ EXPECT_FALSE(span.is_empty());
+ EXPECT_EQ(span[0], 5);
+ EXPECT_EQ(span[1], 5);
+ EXPECT_EQ(span[2], 5);
+ EXPECT_EQ(&span[0], &value);
+ EXPECT_EQ(&span[1], &value);
+ EXPECT_EQ(&span[2], &value);
+ EXPECT_TRUE(span.is_single_element());
+
+ GVSpan converted(span);
+ EXPECT_EQ(converted.type(), CPPType::get<int>());
+ EXPECT_EQ(converted.size(), 3);
+ EXPECT_EQ(converted[0], &value);
+ EXPECT_EQ(converted[1], &value);
+ EXPECT_EQ(converted[2], &value);
+}
+
+TEST(generic_virtual_span, TypeConstructor)
+{
+ GVSpan span(CPPType::get<int32_t>());
+ EXPECT_EQ(span.size(), 0);
+ EXPECT_TRUE(span.is_empty());
+ EXPECT_FALSE(span.is_single_element());
+
+ VSpan<int> converted = span.typed<int>();
+ EXPECT_EQ(converted.size(), 0);
+}
+
+TEST(generic_virtual_span, GenericSpanConstructor)
+{
+ int values[4] = {3, 4, 5, 6};
+ GVSpan span{GSpan(CPPType::get<int32_t>(), values, 4)};
+ EXPECT_EQ(span.size(), 4);
+ EXPECT_FALSE(span.is_empty());
+ EXPECT_EQ(span[0], &values[0]);
+ EXPECT_EQ(span[1], &values[1]);
+ EXPECT_EQ(span[2], &values[2]);
+ EXPECT_EQ(span[3], &values[3]);
+ EXPECT_FALSE(span.is_single_element());
+
+ int materialized[4] = {0};
+ span.materialize_to_uninitialized(materialized);
+ EXPECT_EQ(materialized[0], 3);
+ EXPECT_EQ(materialized[1], 4);
+ EXPECT_EQ(materialized[2], 5);
+ EXPECT_EQ(materialized[3], 6);
+
+ VSpan<int> converted = span.typed<int>();
+ EXPECT_EQ(converted.size(), 4);
+ EXPECT_EQ(converted[0], 3);
+ EXPECT_EQ(converted[1], 4);
+ EXPECT_EQ(converted[2], 5);
+ EXPECT_EQ(converted[3], 6);
+}
+
+TEST(generic_virtual_span, SpanConstructor)
+{
+ std::array<int, 3> values = {6, 7, 8};
+ GVSpan span{Span<int>(values)};
+ EXPECT_EQ(span.type(), CPPType::get<int32_t>());
+ EXPECT_EQ(span.size(), 3);
+ EXPECT_EQ(span[0], &values[0]);
+ EXPECT_EQ(span[1], &values[1]);
+ EXPECT_EQ(span[2], &values[2]);
+ EXPECT_FALSE(span.is_single_element());
+
+ int materialized[3] = {0};
+ span.materialize_to_uninitialized(materialized);
+ EXPECT_EQ(materialized[0], 6);
+ EXPECT_EQ(materialized[1], 7);
+ EXPECT_EQ(materialized[2], 8);
+
+ VSpan<int> converted = span.typed<int>();
+ EXPECT_EQ(converted.size(), 3);
+ EXPECT_EQ(converted[0], 6);
+ EXPECT_EQ(converted[1], 7);
+ EXPECT_EQ(converted[2], 8);
+}
+
+TEST(generic_virtual_span, SingleConstructor)
+{
+ int value = 5;
+ GVSpan span = GVSpan::FromSingle(CPPType::get<int32_t>(), &value, 3);
+ EXPECT_EQ(span.size(), 3);
+ EXPECT_FALSE(span.is_empty());
+ EXPECT_EQ(span[0], &value);
+ EXPECT_EQ(span[1], &value);
+ EXPECT_EQ(span[2], &value);
+ EXPECT_TRUE(span.is_single_element());
+ EXPECT_EQ(span.as_single_element(), &value);
+
+ int materialized[3] = {0};
+ span.materialize_to_uninitialized({1, 2}, materialized);
+ EXPECT_EQ(materialized[0], 0);
+ EXPECT_EQ(materialized[1], 5);
+ EXPECT_EQ(materialized[2], 5);
+
+ VSpan<int> converted = span.typed<int>();
+ EXPECT_EQ(converted.size(), 3);
+ EXPECT_EQ(converted[0], 5);
+ EXPECT_EQ(converted[1], 5);
+ EXPECT_EQ(converted[2], 5);
+}
+
+} // namespace blender::fn::tests