diff options
Diffstat (limited to 'libs/ftl')
-rw-r--r-- | libs/ftl/Android.bp | 1 | ||||
-rw-r--r-- | libs/ftl/function_test.cpp | 379 |
2 files changed, 380 insertions, 0 deletions
diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp index ea1b5e4998..918680d6a7 100644 --- a/libs/ftl/Android.bp +++ b/libs/ftl/Android.bp @@ -17,6 +17,7 @@ cc_test { "enum_test.cpp", "fake_guard_test.cpp", "flags_test.cpp", + "function_test.cpp", "future_test.cpp", "match_test.cpp", "mixins_test.cpp", diff --git a/libs/ftl/function_test.cpp b/libs/ftl/function_test.cpp new file mode 100644 index 0000000000..91b5e08041 --- /dev/null +++ b/libs/ftl/function_test.cpp @@ -0,0 +1,379 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * 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. + */ + +#include <ftl/function.h> +#include <gtest/gtest.h> + +#include <array> +#include <cstddef> +#include <cstdint> +#include <string_view> +#include <type_traits> + +namespace android::test { +namespace { + +// Create an alias to composite requirements defined by the trait class `T` for easier testing. +template <typename T, typename S> +inline constexpr bool is_opaquely_storable = (T::template require_trivially_copyable<S> && + T::template require_trivially_destructible<S> && + T::template require_will_fit_in_opaque_storage<S> && + T::template require_alignment_compatible<S>); + +// `I` gives a count of sizeof(std::intptr_t) bytes , and `J` gives a raw count of bytes +template <size_t I, size_t J = 0> +struct KnownSizeFunctionObject { + using Data = std::array<std::byte, sizeof(std::intptr_t) * I + J>; + void operator()() const {}; + Data data{}; +}; + +} // namespace + +// static_assert the expected type traits +static_assert(std::is_invocable_r_v<void, ftl::Function<void()>>); +static_assert(std::is_trivially_copyable_v<ftl::Function<void()>>); +static_assert(std::is_trivially_destructible_v<ftl::Function<void()>>); +static_assert(std::is_trivially_copy_constructible_v<ftl::Function<void()>>); +static_assert(std::is_trivially_move_constructible_v<ftl::Function<void()>>); +static_assert(std::is_trivially_copy_assignable_v<ftl::Function<void()>>); +static_assert(std::is_trivially_move_assignable_v<ftl::Function<void()>>); + +template <typename T> +using function_traits = ftl::details::function_traits<T>; + +// static_assert that the expected value of N is used for known function object sizes. +static_assert(function_traits<KnownSizeFunctionObject<0, 0>>::size == 0); +static_assert(function_traits<KnownSizeFunctionObject<0, 1>>::size == 0); +static_assert(function_traits<KnownSizeFunctionObject<1, 0>>::size == 0); +static_assert(function_traits<KnownSizeFunctionObject<1, 1>>::size == 1); +static_assert(function_traits<KnownSizeFunctionObject<2, 0>>::size == 1); +static_assert(function_traits<KnownSizeFunctionObject<2, 1>>::size == 2); + +// Check that is_function_v works +static_assert(!ftl::is_function_v<KnownSizeFunctionObject<0>>); +static_assert(!ftl::is_function_v<std::function<void()>>); +static_assert(ftl::is_function_v<ftl::Function<void()>>); + +// static_assert what can and cannot be stored inside the opaque storage + +template <size_t N> +using function_opaque_storage = ftl::details::function_opaque_storage<N>; + +// Function objects can be stored if they fit. +static_assert(is_opaquely_storable<function_opaque_storage<0>, KnownSizeFunctionObject<0>>); +static_assert(is_opaquely_storable<function_opaque_storage<0>, KnownSizeFunctionObject<1>>); +static_assert(!is_opaquely_storable<function_opaque_storage<0>, KnownSizeFunctionObject<2>>); + +static_assert(is_opaquely_storable<function_opaque_storage<1>, KnownSizeFunctionObject<2>>); +static_assert(!is_opaquely_storable<function_opaque_storage<1>, KnownSizeFunctionObject<3>>); + +static_assert(is_opaquely_storable<function_opaque_storage<2>, KnownSizeFunctionObject<3>>); +static_assert(!is_opaquely_storable<function_opaque_storage<2>, KnownSizeFunctionObject<4>>); + +// Another opaque storage can be stored if it fits. This property is used to copy smaller +// ftl::Functions into larger ones. +static_assert(is_opaquely_storable<function_opaque_storage<2>, function_opaque_storage<0>::type>); +static_assert(is_opaquely_storable<function_opaque_storage<2>, function_opaque_storage<1>::type>); +static_assert(is_opaquely_storable<function_opaque_storage<2>, function_opaque_storage<2>::type>); +static_assert(!is_opaquely_storable<function_opaque_storage<2>, function_opaque_storage<3>::type>); + +// Function objects that aren't trivially copyable or destroyable cannot be stored. +auto lambda_capturing_unique_ptr = [ptr = std::unique_ptr<void*>()] { static_cast<void>(ptr); }; +static_assert( + !is_opaquely_storable<function_opaque_storage<2>, decltype(lambda_capturing_unique_ptr)>); + +// Keep in sync with "Example usage" in header file. +TEST(Function, Example) { + using namespace std::string_view_literals; + + class MyClass { + public: + void on_event() const {} + int on_string(int*, std::string_view) { return 1; } + + auto get_function() { + return ftl::make_function([this] { on_event(); }); + } + } cls; + + // A function container with no arguments, and returning no value. + ftl::Function<void()> f; + + // Construct a ftl::Function containing a small lambda. + f = cls.get_function(); + + // Construct a ftl::Function that calls `cls.on_event()`. + f = ftl::make_function<&MyClass::on_event>(&cls); + + // Create a do-nothing function. + f = ftl::no_op; + + // Invoke the contained function. + f(); + + // Also invokes it. + std::invoke(f); + + // Create a typedef to give a more meaningful name and bound the size. + using MyFunction = ftl::Function<int(std::string_view), 2>; + int* ptr = nullptr; + auto f1 = + MyFunction::make([cls = &cls, ptr](std::string_view sv) { return cls->on_string(ptr, sv); }); + int r = f1("abc"sv); + + // Returns a default-constructed int (0). + f1 = ftl::no_op; + r = f1("abc"sv); + EXPECT_EQ(r, 0); +} + +TEST(Function, BasicOperations) { + // Default constructible. + ftl::Function<int()> f; + + // Compares as empty + EXPECT_FALSE(f); + EXPECT_TRUE(f == nullptr); + EXPECT_FALSE(f != nullptr); + EXPECT_TRUE(ftl::Function<int()>() == f); + EXPECT_FALSE(ftl::Function<int()>() != f); + + // Assigning no_op sets it to not empty. + f = ftl::no_op; + + // Verify it can be called, and that it returns a default constructed value. + EXPECT_EQ(f(), 0); + + // Comparable when non-empty. + EXPECT_TRUE(f); + EXPECT_FALSE(f == nullptr); + EXPECT_TRUE(f != nullptr); + EXPECT_FALSE(ftl::Function<int()>() == f); + EXPECT_TRUE(ftl::Function<int()>() != f); + + // Constructing from nullptr means empty. + f = ftl::Function<int()>{nullptr}; + EXPECT_FALSE(f); + + // Assigning nullptr means it is empty. + f = nullptr; + EXPECT_FALSE(f); + + // Move construction + f = ftl::no_op; + ftl::Function<int()> g{std::move(f)}; + EXPECT_TRUE(g != nullptr); + + // Move assignment + f = nullptr; + f = std::move(g); + EXPECT_TRUE(f != nullptr); + + // Copy construction + ftl::Function<int()> h{f}; + EXPECT_TRUE(h != nullptr); + + // Copy assignment + g = h; + EXPECT_TRUE(g != nullptr); +} + +TEST(Function, CanMoveConstructFromLambda) { + auto lambda = [] {}; + ftl::Function<void()> f{std::move(lambda)}; +} + +TEST(Function, TerseDeducedConstructAndAssignFromLambda) { + auto f = ftl::Function([] { return 1; }); + EXPECT_EQ(f(), 1); + + f = [] { return 2; }; + EXPECT_EQ(f(), 2); +} + +namespace { + +struct ImplicitConversionsHelper { + auto exact(int) -> int { return 0; } + auto inexact(long) -> short { return 0; } + // TODO: Switch to `auto templated(auto x)` with C++20 + template <typename T> + T templated(T x) { + return x; + } + + static auto static_exact(int) -> int { return 0; } + static auto static_inexact(long) -> short { return 0; } + // TODO: Switch to `static auto static_templated(auto x)` with C++20 + template <typename T> + static T static_templated(T x) { + return x; + } +}; + +} // namespace + +TEST(Function, ImplicitConversions) { + using Function = ftl::Function<int(int)>; + auto check = [](Function f) { return f(0); }; + auto exact = [](int) -> int { return 0; }; + auto inexact = [](long) -> short { return 0; }; + auto templated = [](auto x) { return x; }; + + ImplicitConversionsHelper helper; + + // Note, `check(nullptr)` would crash, so we can only check if it would be invocable. + static_assert(std::is_invocable_v<decltype(check), decltype(nullptr)>); + + // Note: We invoke each of these to fully expand all the templates involved. + EXPECT_EQ(check(ftl::no_op), 0); + + EXPECT_EQ(check(exact), 0); + EXPECT_EQ(check(inexact), 0); + EXPECT_EQ(check(templated), 0); + + EXPECT_EQ(check(Function::make<&ImplicitConversionsHelper::exact>(&helper)), 0); + EXPECT_EQ(check(Function::make<&ImplicitConversionsHelper::inexact>(&helper)), 0); + EXPECT_EQ(check(Function::make<&ImplicitConversionsHelper::templated<int>>(&helper)), 0); + + EXPECT_EQ(check(Function::make<&ImplicitConversionsHelper::static_exact>()), 0); + EXPECT_EQ(check(Function::make<&ImplicitConversionsHelper::static_inexact>()), 0); + EXPECT_EQ(check(Function::make<&ImplicitConversionsHelper::static_templated<int>>()), 0); +} + +TEST(Function, MakeWithNonConstMemberFunction) { + struct Observer { + bool called = false; + void setCalled() { called = true; } + } observer; + + auto f = ftl::make_function<&Observer::setCalled>(&observer); + + f(); + + EXPECT_TRUE(observer.called); + + EXPECT_TRUE(f == ftl::Function<void()>::make<&Observer::setCalled>(&observer)); +} + +TEST(Function, MakeWithConstMemberFunction) { + struct Observer { + mutable bool called = false; + void setCalled() const { called = true; } + } observer; + + const auto f = ftl::make_function<&Observer::setCalled>(&observer); + + f(); + + EXPECT_TRUE(observer.called); + + EXPECT_TRUE(f == ftl::Function<void()>::make<&Observer::setCalled>(&observer)); +} + +TEST(Function, MakeWithConstClassPointer) { + const struct Observer { + mutable bool called = false; + void setCalled() const { called = true; } + } observer; + + const auto f = ftl::make_function<&Observer::setCalled>(&observer); + + f(); + + EXPECT_TRUE(observer.called); + + EXPECT_TRUE(f == ftl::Function<void()>::make<&Observer::setCalled>(&observer)); +} + +TEST(Function, MakeWithNonCapturingLambda) { + auto f = ftl::make_function([](int a, int b) { return a + b; }); + EXPECT_EQ(f(1, 2), 3); +} + +TEST(Function, MakeWithCapturingLambda) { + bool called = false; + auto f = ftl::make_function([&called](int a, int b) { + called = true; + return a + b; + }); + EXPECT_EQ(f(1, 2), 3); + EXPECT_TRUE(called); +} + +TEST(Function, MakeWithCapturingMutableLambda) { + bool called = false; + auto f = ftl::make_function([&called](int a, int b) mutable { + called = true; + return a + b; + }); + EXPECT_EQ(f(1, 2), 3); + EXPECT_TRUE(called); +} + +TEST(Function, MakeWithThreePointerCapturingLambda) { + bool my_bool = false; + int my_int = 0; + float my_float = 0.f; + + auto f = ftl::make_function( + [ptr_bool = &my_bool, ptr_int = &my_int, ptr_float = &my_float](int a, int b) mutable { + *ptr_bool = true; + *ptr_int = 1; + *ptr_float = 1.f; + + return a + b; + }); + + EXPECT_EQ(f(1, 2), 3); + + EXPECT_TRUE(my_bool); + EXPECT_EQ(my_int, 1); + EXPECT_EQ(my_float, 1.f); +} + +TEST(Function, MakeWithFreeFunction) { + auto f = ftl::make_function<&std::make_unique<int, int>>(); + std::unique_ptr<int> unique_int = f(1); + ASSERT_TRUE(unique_int); + EXPECT_EQ(*unique_int, 1); +} + +TEST(Function, CopyToLarger) { + int counter = 0; + ftl::Function<void()> a{[ptr_counter = &counter] { (*ptr_counter)++; }}; + ftl::Function<void(), 1> b = a; + ftl::Function<void(), 2> c = a; + + EXPECT_EQ(counter, 0); + a(); + EXPECT_EQ(counter, 1); + b(); + EXPECT_EQ(counter, 2); + c(); + EXPECT_EQ(counter, 3); + + b = [ptr_counter = &counter] { (*ptr_counter) += 2; }; + c = [ptr_counter = &counter] { (*ptr_counter) += 3; }; + + b(); + EXPECT_EQ(counter, 5); + c(); + EXPECT_EQ(counter, 8); +} + +} // namespace android::test |