// Copyright (c) 2020 - 2021 by Apex.AI Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // SPDX-License-Identifier: Apache-2.0 #include "iceoryx_hoofs/cxx/function.hpp" #include "test.hpp" #include #include using namespace ::testing; using namespace iox::cxx; namespace { using std::cout; using std::endl; constexpr uint64_t Bytes = 128U; using signature = int32_t(int32_t); template using fixed_size_function = iox::cxx::function; using test_function = fixed_size_function; // helper template to count construction and copy statistics, // for our purpose (test_function) we do not need to distinguish // between copy (move) ctor and copy(move) assignment template class Counter { public: static uint64_t numCreated; static uint64_t numCopied; static uint64_t numMoved; static uint64_t numDestroyed; Counter() { ++numCreated; } Counter(const Counter&) { ++numCreated; ++numCopied; } Counter(Counter&&) { ++numMoved; } ~Counter() { ++numDestroyed; } Counter& operator=(const Counter&) { ++numCopied; } Counter& operator=(Counter&&) { ++numMoved; } static void resetCounts() { numCreated = 0U; numCopied = 0U; numMoved = 0U; numDestroyed = 0U; } }; template uint64_t Counter::numCreated = 0U; template uint64_t Counter::numCopied = 0U; template uint64_t Counter::numMoved = 0U; template uint64_t Counter::numDestroyed = 0U; class Functor : public Counter { public: Functor(int32_t state) : Counter() , m_state(state) { } int32_t operator()(int32_t n) { m_state += n; return m_state; } // integer arg to satisfy signature requirement of our test_function int32_t getState(int32_t n = 0) const { return m_state + n; } int32_t m_state{0}; }; int32_t freeFunction(int32_t n) { return n + 1; }; struct Arg : Counter { Arg() = default; Arg(uint32_t value) : value(value){}; Arg(const Arg&) = default; Arg& operator=(const Arg&) = default; // We cannot delete the move ctor, the function wrapper requires the arguments to be copy-constructible. // According to the standard this means the copy Ctor must exist and move cannot be explicitly deleted. // Move does not necessarily have to be defined, in which case the compiler will perform a copy // whenever a move would be possible. // Note that this is mainly an issue if the argument is passed by value. // The std::function also fails to compile in this case (gcc implementation). int32_t value; }; int32_t freeFunctionWithCopyableArg(Arg arg) { return arg.value; } class function_test : public Test { public: void SetUp() override { } void TearDown() override { } static int32_t staticFunction(int32_t n) { return n + 1; } }; TEST_F(function_test, DefaultConstructionCreatesNoCallable) { ::testing::Test::RecordProperty("TEST_ID", "18b1f6fe-df79-495b-bd14-c4b9a3dba44a"); test_function sut; EXPECT_FALSE(sut.operator bool()); } TEST_F(function_test, ConstructionFromFunctionNullPointerIsNotCallable) { ::testing::Test::RecordProperty("TEST_ID", "e07d3ea5-1ace-4047-b796-a423d4e62421"); int (*fp)(int) = nullptr; test_function sut(fp); EXPECT_FALSE(sut.operator bool()); } TEST_F(function_test, ConstructionFromFunctorIsCallable) { ::testing::Test::RecordProperty("TEST_ID", "2969913d-a849-47c5-a76b-f32481e03ea5"); Functor f(73); Functor::resetCounts(); test_function sut(f); EXPECT_EQ(Functor::numCreated, 1U); ASSERT_TRUE(sut.operator bool()); EXPECT_EQ(sut(1), f(1)); } TEST_F(function_test, ConstructionFromLambdaIsCallable) { ::testing::Test::RecordProperty("TEST_ID", "f42a8511-b78d-47b5-aa7f-ae227ae12465"); int32_t capture = 37; auto lambda = [state = capture](int32_t n) { return state + n; }; test_function sut(lambda); ASSERT_TRUE(sut.operator bool()); EXPECT_EQ(sut(1), lambda(1)); } TEST_F(function_test, ConstructionFromFreeFunctionIsCallable) { ::testing::Test::RecordProperty("TEST_ID", "2d808b65-182b-44b0-a501-c9b6ab3c80e7"); test_function sut(freeFunction); ASSERT_TRUE(sut.operator bool()); EXPECT_EQ(sut(1), freeFunction(1)); } TEST_F(function_test, ConstructionFromStaticFunctionIsCallable) { ::testing::Test::RecordProperty("TEST_ID", "24f95326-9d93-4ce1-8338-3582d9a82af3"); // is essentially also a free function but we test the case to be sure test_function sut(staticFunction); ASSERT_TRUE(sut.operator bool()); EXPECT_EQ(sut(1), staticFunction(1)); } TEST_F(function_test, ConstructionFromMemberFunctionIsCallable) { ::testing::Test::RecordProperty("TEST_ID", "ac4311a5-8e85-4051-92cc-ca28e679c5ab"); Functor f(37); test_function sut(f, &Functor::operator()); ASSERT_TRUE(sut.operator bool()); auto result = f(1); EXPECT_EQ(sut(1), result + 1); } TEST_F(function_test, ConstructionFromConstMemberFunctionIsCallable) { ::testing::Test::RecordProperty("TEST_ID", "a59e5060-ebca-42dd-ae04-0bacab7c3805"); Functor f(37); test_function sut(f, &Functor::getState); ASSERT_TRUE(sut.operator bool()); auto state = f.getState(1); EXPECT_EQ(sut(1), state); EXPECT_EQ(f.getState(1), state); // state is unchanged by the previous call } TEST_F(function_test, ConstructionFromAnotherFunctionIsCallable) { ::testing::Test::RecordProperty("TEST_ID", "18e62771-8ed3-43eb-ba1d-876f3825e09e"); constexpr int32_t INITIAL = 37; int32_t capture = INITIAL; auto lambda = [&](int32_t n) { return ++capture + n; }; function f(lambda); // the other function type must be small enough to fit test_function sut(f); ASSERT_TRUE(sut.operator bool()); auto result = f(1); EXPECT_EQ(sut(1), result + 1); EXPECT_EQ(capture, INITIAL + 2); } TEST_F(function_test, FunctionStateIsIndependentOfSource) { ::testing::Test::RecordProperty("TEST_ID", "8302046f-cd6a-4527-aca6-3e6408f87a6b"); constexpr uint32_t INITIAL_STATE = 73U; static_storage<1024U> storage; auto p = storage.allocate(); p = new (p) Functor(INITIAL_STATE); // call the dtor in any case (even if the test fails due to ASSERT) std::unique_ptr guard(p, [](Functor* f) { f->~Functor(); }); auto& functor = *p; // test whether the function really owns the functor // (no dependency or side effects) test_function sut(functor); ASSERT_TRUE(sut.operator bool()); // both increment their state independently EXPECT_EQ(sut(1U), functor(1U)); guard.reset(); // call the deleter storage.clear(); EXPECT_EQ(sut(1U), INITIAL_STATE + 2U); } // The implementation uses type erasure and we need to verify that the corresponding // constructors and operators of the underlying object (functor) are called. TEST_F(function_test, DestructorCallsDestructorOfStoredFunctor) { ::testing::Test::RecordProperty("TEST_ID", "2481cf93-c63b-40b0-b6de-2213507efe33"); Functor f(73); Functor::resetCounts(); { test_function sut(f); } EXPECT_EQ(Functor::numDestroyed, 1U); } TEST_F(function_test, CopyCtorCopiesStoredFunctor) { ::testing::Test::RecordProperty("TEST_ID", "e34fba7e-0c11-4535-8ac3-7d1b034fc793"); Functor functor(73); test_function f(functor); Functor::resetCounts(); test_function sut(f); EXPECT_EQ(Functor::numCopied, 1U); ASSERT_TRUE(sut.operator bool()); ASSERT_TRUE(f.operator bool()); EXPECT_EQ(sut(1), f(1)); } TEST_F(function_test, MoveCtorMovesStoredFunctor) { ::testing::Test::RecordProperty("TEST_ID", "0b9d8b1e-81a6-4242-8f31-aadf2a6c0f91"); Functor functor(73); test_function f(functor); Functor::resetCounts(); test_function sut(std::move(f)); EXPECT_EQ(Functor::numMoved, 1U); ASSERT_TRUE(sut.operator bool()); EXPECT_EQ(sut(1), functor(1)); EXPECT_FALSE(f.operator bool()); } TEST_F(function_test, CopyAssignmentCopiesStoredFunctor) { ::testing::Test::RecordProperty("TEST_ID", "8ef88318-0aa0-4766-8b3c-a9cc197f88fd"); test_function f(Functor(73)); test_function sut(Functor(42)); Functor::resetCounts(); sut = f; EXPECT_EQ(Functor::numDestroyed, 1U); EXPECT_EQ(Functor::numCopied, 1U); ASSERT_TRUE(sut.operator bool()); ASSERT_TRUE(f.operator bool()); EXPECT_EQ(sut(1), f(1)); } TEST_F(function_test, MoveAssignmentMovesStoredFunctor) { ::testing::Test::RecordProperty("TEST_ID", "684f7c51-5532-46d1-91ea-7e7e7e76534b"); Functor functor(73); test_function f(functor); test_function sut(Functor(42)); Functor::resetCounts(); sut = std::move(f); // destroy previous Functor in sut and Functor in f after move // (f is not callable but can be reassigned) EXPECT_EQ(Functor::numDestroyed, 2U); EXPECT_EQ(Functor::numMoved, 1U); ASSERT_TRUE(sut.operator bool()); EXPECT_EQ(sut(1), functor(1)); EXPECT_FALSE(f.operator bool()); } TEST_F(function_test, CopyCtorCopiesStoredFreeFunction) { ::testing::Test::RecordProperty("TEST_ID", "8f95a82a-c879-48b1-aa56-316bf15b983a"); test_function f(freeFunction); test_function sut(f); ASSERT_TRUE(sut.operator bool()); ASSERT_TRUE(f.operator bool()); EXPECT_EQ(sut(1), f(1)); } TEST_F(function_test, MoveCtorMovesStoredFreeFunction) { ::testing::Test::RecordProperty("TEST_ID", "efcd5ae0-393f-4243-8825-871f7f59a9c0"); test_function f(freeFunction); test_function sut(std::move(f)); ASSERT_TRUE(sut.operator bool()); EXPECT_EQ(sut(1), freeFunction(1)); EXPECT_FALSE(f.operator bool()); } TEST_F(function_test, CopyAssignmentCopiesStoredFreeFunction) { ::testing::Test::RecordProperty("TEST_ID", "29ebca31-0266-4741-84b3-b3cbecfc7b4a"); test_function f(freeFunction); test_function sut(Functor(73)); Functor::resetCounts(); sut = f; EXPECT_EQ(Functor::numDestroyed, 1U); EXPECT_EQ(Functor::numCopied, 0U); EXPECT_EQ(Functor::numMoved, 0U); ASSERT_TRUE(sut.operator bool()); ASSERT_TRUE(f.operator bool()); EXPECT_EQ(sut(1), f(1)); } TEST_F(function_test, MoveAssignmentMovesStoredFreeFunction) { ::testing::Test::RecordProperty("TEST_ID", "414ec34a-f5e3-4ab6-bfab-60796bbd7b8a"); test_function f(freeFunction); test_function sut(Functor(73)); Functor::resetCounts(); sut = std::move(f); EXPECT_EQ(Functor::numDestroyed, 1U); EXPECT_EQ(Functor::numCopied, 0U); EXPECT_EQ(Functor::numMoved, 0U); ASSERT_TRUE(sut.operator bool()); EXPECT_EQ(sut(1), freeFunction(1)); EXPECT_FALSE(f.operator bool()); } TEST_F(function_test, CopiedNonCallableFunctionIsNotCallable) { ::testing::Test::RecordProperty("TEST_ID", "627cc564-8dcb-4656-9042-d99011907af5"); test_function f; Functor::resetCounts(); test_function sut(f); EXPECT_EQ(Functor::numCopied, 0U); EXPECT_EQ(Functor::numMoved, 0U); EXPECT_FALSE(sut.operator bool()); EXPECT_FALSE(f.operator bool()); } TEST_F(function_test, MovedNonCallableFunctionIsNotCallable) { ::testing::Test::RecordProperty("TEST_ID", "54ae2f6d-b731-44f5-a611-33b13db1d820"); test_function f; Functor::resetCounts(); test_function sut(std::move(f)); EXPECT_EQ(Functor::numCopied, 0U); EXPECT_EQ(Functor::numMoved, 0U); EXPECT_FALSE(sut.operator bool()); EXPECT_FALSE(f.operator bool()); } TEST_F(function_test, CopyAssignedNonCallableFunctionIsNotCallable) { ::testing::Test::RecordProperty("TEST_ID", "fa30787b-5726-4e38-9537-b7662294df4d"); test_function f; test_function sut(Functor(73)); Functor::resetCounts(); sut = f; EXPECT_EQ(Functor::numDestroyed, 1U); EXPECT_EQ(Functor::numCopied, 0U); EXPECT_EQ(Functor::numMoved, 0U); EXPECT_FALSE(sut.operator bool()); EXPECT_FALSE(f.operator bool()); } TEST_F(function_test, MoveAssignedNonCallableFunctionIsNotCallable) { ::testing::Test::RecordProperty("TEST_ID", "c98885d8-42e0-4cd4-bd74-0dd6a750ef90"); test_function f; test_function sut(Functor(73)); Functor::resetCounts(); sut = std::move(f); EXPECT_EQ(Functor::numDestroyed, 1U); EXPECT_EQ(Functor::numCopied, 0U); EXPECT_EQ(Functor::numMoved, 0U); EXPECT_FALSE(sut.operator bool()); EXPECT_FALSE(f.operator bool()); } TEST_F(function_test, MemberSwapWorks) { ::testing::Test::RecordProperty("TEST_ID", "85ba9d33-f552-4aa9-a214-24464a5ca934"); Functor f1(73); Functor f2(37); test_function sut1(f1); test_function sut2(f2); sut1.swap(sut2); ASSERT_TRUE(sut1.operator bool()); EXPECT_EQ(sut1(1), f2(1)); ASSERT_TRUE(sut2.operator bool()); EXPECT_EQ(sut2(1), f1(1)); } TEST_F(function_test, StaticSwapWorks) { ::testing::Test::RecordProperty("TEST_ID", "0b27cb60-85ae-4942-b448-1f9b00a253fa"); Functor f1(73); Functor f2(37); test_function sut1(f1); test_function sut2(f2); swap(sut1, sut2); ASSERT_TRUE(sut1.operator bool()); EXPECT_EQ(sut1(1), f2(1)); ASSERT_TRUE(sut2.operator bool()); EXPECT_EQ(sut2(1), f1(1)); } TEST_F(function_test, FunctorOfSizeSmallerThanStorageBytesCanBeStored) { ::testing::Test::RecordProperty("TEST_ID", "34de556c-95f4-4d7b-b01b-377c08529f62"); // it will not compile if the storage is too small, constexpr auto BYTES = test_function::required_storage_size(); EXPECT_LE(sizeof(Functor), BYTES); Functor f(73); iox::cxx::function sut(f); EXPECT_TRUE(sut.operator bool()); } TEST_F(function_test, IsStorableIsConsistent) { ::testing::Test::RecordProperty("TEST_ID", "78fd4207-9ef4-459d-96f4-9cca98135b47"); constexpr auto BYTES = test_function::required_storage_size(); constexpr auto RESULT = iox::cxx::function::is_storable(); EXPECT_TRUE(RESULT); } TEST_F(function_test, IsNotStorableDueToSize) { ::testing::Test::RecordProperty("TEST_ID", "4ecd7078-5b3d-4fd5-b5af-296401b652ce"); constexpr auto BYTES = test_function::required_storage_size(); constexpr auto RESULT = iox::cxx::function::is_storable(); EXPECT_FALSE(RESULT); } TEST_F(function_test, IsNotStorableDueToSignature) { ::testing::Test::RecordProperty("TEST_ID", "a7a5e2a6-68dd-477a-8eb0-573e57c7a3ae"); auto nonStorable = []() {}; using NonStorable = decltype(nonStorable); constexpr auto BYTES = test_function::required_storage_size(); constexpr auto RESULT = iox::cxx::function::is_storable(); EXPECT_FALSE(RESULT); } TEST_F(function_test, CallWithCopyConstructibleArgument) { ::testing::Test::RecordProperty("TEST_ID", "20018d76-6255-407a-b3d3-77b6b480067d"); iox::cxx::function sut(freeFunctionWithCopyableArg); std::function func(freeFunctionWithCopyableArg); Arg::resetCounts(); Arg arg(73); auto result = sut(arg); EXPECT_EQ(result, freeFunctionWithCopyableArg(arg)); EXPECT_EQ(result, func(arg)); // note that by using the numCopies counter we can observe that the std::function call also performs 2 copies of arg // in this case } TEST_F(function_test, CallWithVoidSignatureWorks) { ::testing::Test::RecordProperty("TEST_ID", "dcc54ea2-ce1a-4142-a141-df6d0bbe9707"); const int32_t initial = 73; int value = initial; auto lambda = [&]() { ++value; }; iox::cxx::function sut(lambda); ASSERT_TRUE(sut.operator bool()); sut(); EXPECT_EQ(value, initial + 1); } TEST_F(function_test, CallWithReferenceArgumentsWorks) { ::testing::Test::RecordProperty("TEST_ID", "ef3fe399-cf1c-4d28-b688-b50ac9c1fe3e"); const int32_t initial = 73; Arg arg(initial); auto lambda = [](Arg& a) { ++a.value; }; iox::cxx::function sut(lambda); ASSERT_TRUE(sut.operator bool()); sut(arg); EXPECT_EQ(arg.value, initial + 1); } TEST_F(function_test, CallWithConstReferenceArgumentsWorks) { ::testing::Test::RecordProperty("TEST_ID", "80ea9066-918e-436d-9b99-11c6339412da"); const int32_t initial = 73; Arg arg(initial); auto lambda = [](const Arg& a) { return a.value + 1; }; iox::cxx::function sut(lambda); ASSERT_TRUE(sut.operator bool()); auto result = sut(arg); EXPECT_EQ(result, initial + 1); } TEST_F(function_test, CallWithValueArgumentsWorks) { ::testing::Test::RecordProperty("TEST_ID", "b3ea6823-b392-418e-8be0-e8d69246e3c5"); const int32_t initial = 73; Arg arg(initial); auto lambda = [](Arg a) { return a.value + 1; }; iox::cxx::function sut(lambda); ASSERT_TRUE(sut.operator bool()); auto result = sut(arg); EXPECT_EQ(result, initial + 1); } TEST_F(function_test, CallWithRValueReferenceArgumentsWorks) { ::testing::Test::RecordProperty("TEST_ID", "1c827680-a04d-4fca-bb22-96922d7192ab"); const int32_t initial = 73; Arg arg(initial); auto lambda = [](Arg&& a) { return a.value + 1; }; iox::cxx::function sut(lambda); ASSERT_TRUE(sut.operator bool()); auto result = sut(std::move(arg)); EXPECT_EQ(result, initial + 1); } TEST_F(function_test, CallWithMixedArgumentsWorks) { ::testing::Test::RecordProperty("TEST_ID", "d26e380d-4b0e-4c9f-a1b9-c9e7ab3707e1"); Arg arg1(1); Arg arg2(2); Arg arg3(3); Arg arg4(4); constexpr int32_t sum = 10; auto lambda = [](Arg& a1, const Arg& a2, Arg&& a3, Arg a4) { return a1.value + a2.value + a3.value + a4.value; }; iox::cxx::function sut(lambda); ASSERT_TRUE(sut.operator bool()); auto result = sut(arg1, arg2, std::move(arg3), arg4); EXPECT_EQ(result, sum); } } // namespace