From 78e9acd9672649f2660ebd054bd1ce16828d992f Mon Sep 17 00:00:00 2001 From: Karl Wiberg Date: Tue, 15 Sep 2020 11:06:34 +0200 Subject: [PATCH] UntypedFunction: Add unit tests and fix a few issues Bug: webrtc:11943 Change-Id: I1f3c0495612148546ec399a800f97fe88b439c83 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/184260 Commit-Queue: Karl Wiberg Reviewed-by: Mirko Bonadei Cr-Commit-Position: refs/heads/master@{#32116} --- BUILD.gn | 1 + rtc_base/BUILD.gn | 9 + rtc_base/function.h | 26 ++- rtc_base/function_unittest.cc | 307 ++++++++++++++++++++++++++++++++++ 4 files changed, 339 insertions(+), 4 deletions(-) create mode 100644 rtc_base/function_unittest.cc diff --git a/BUILD.gn b/BUILD.gn index 443a4ff190..af3a690080 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -538,6 +538,7 @@ if (rtc_include_tests) { "call:fake_network_pipe_unittests", "p2p:libstunprober_unittests", "p2p:rtc_p2p_unittests", + "rtc_base:function_unittest", "rtc_base:rtc_base_approved_unittests", "rtc_base:rtc_base_unittests", "rtc_base:rtc_json_unittests", diff --git a/rtc_base/BUILD.gn b/rtc_base/BUILD.gn index 1d4a28d3e2..105bbc3c9f 100644 --- a/rtc_base/BUILD.gn +++ b/rtc_base/BUILD.gn @@ -1158,6 +1158,15 @@ if (rtc_include_tests) { ] } + rtc_library("function_unittest") { + testonly = true + sources = [ "function_unittest.cc" ] + deps = [ + ":function", + "../test:test_support", + ] + } + rtc_library("rtc_base_nonparallel_tests") { testonly = true diff --git a/rtc_base/function.h b/rtc_base/function.h index 67fcd59653..cbc5f7dabd 100644 --- a/rtc_base/function.h +++ b/rtc_base/function.h @@ -102,9 +102,10 @@ class UntypedFunction final { } else { // The callable is either nontrivial or too large, so we can't keep it // in the inline storage; use the heap instead. + webrtc_function_impl::VoidUnion vu; + vu.void_ptr = new F_deref(std::forward(f)); return UntypedFunction( - webrtc_function_impl::VoidUnion{.void_ptr = - new F_deref(std::forward(f))}, + vu, reinterpret_cast( webrtc_function_impl::CallHelpers< Signature>::template CallVoidPtr), @@ -123,8 +124,10 @@ class UntypedFunction final { // the result is an empty UntypedFunction. template static UntypedFunction Create(Signature* f) { + webrtc_function_impl::VoidUnion vu; + vu.fun_ptr = reinterpret_cast(f); return UntypedFunction( - reinterpret_cast(f), + vu, f ? reinterpret_cast( webrtc_function_impl::CallHelpers::CallFunPtr) : nullptr, @@ -134,6 +137,18 @@ class UntypedFunction final { // Default constructor. Creates an empty UntypedFunction. UntypedFunction() : call_(nullptr), delete_(nullptr) {} + // Nullptr constructor and assignment. Creates an empty UntypedFunction. + UntypedFunction(std::nullptr_t) // NOLINT(runtime/explicit) + : call_(nullptr), delete_(nullptr) {} + UntypedFunction& operator=(std::nullptr_t) { + call_ = nullptr; + if (delete_) { + delete_(&f_); + delete_ = nullptr; + } + return *this; + } + // Not copyable. UntypedFunction(const UntypedFunction&) = delete; UntypedFunction& operator=(const UntypedFunction&) = delete; @@ -144,6 +159,9 @@ class UntypedFunction final { other.delete_ = nullptr; } UntypedFunction& operator=(UntypedFunction&& other) { + if (delete_) { + delete_(&f_); + } f_ = other.f_; call_ = other.call_; delete_ = other.delete_; @@ -169,7 +187,7 @@ class UntypedFunction final { template typename webrtc_function_impl::CallHelpers::return_type Call( - ArgT... args) { + ArgT&&... args) { return webrtc_function_impl::CallHelpers::DoCall( call_, &f_, std::forward(args)...); } diff --git a/rtc_base/function_unittest.cc b/rtc_base/function_unittest.cc new file mode 100644 index 0000000000..7a3d658917 --- /dev/null +++ b/rtc_base/function_unittest.cc @@ -0,0 +1,307 @@ +/* + * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "rtc_base/function.h" + +#include +#include + +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +using ::testing::Pointee; + +TEST(UntypedFunction, Empty1) { + UntypedFunction uf; + EXPECT_FALSE(uf); + EXPECT_TRUE(uf.IsTriviallyDestructible()); +} + +TEST(UntypedFunction, Empty2) { + UntypedFunction uf = nullptr; + EXPECT_FALSE(uf); + EXPECT_TRUE(uf.IsTriviallyDestructible()); +} + +TEST(UntypedFunction, Empty3) { + UntypedFunction uf = UntypedFunction::Create(nullptr); + EXPECT_FALSE(uf); + EXPECT_TRUE(uf.IsTriviallyDestructible()); +} + +TEST(UntypedFunction, CallTrivialWithInt) { + auto uf = UntypedFunction::Create([](int x) { return x + 5; }); + EXPECT_TRUE(uf); + EXPECT_TRUE(uf.IsTriviallyDestructible()); + EXPECT_EQ(uf.Call(17), 22); +} + +TEST(UntypedFunction, CallTrivialWithPointer) { + auto uf = UntypedFunction::Create([](int* x) { return *x; }); + EXPECT_TRUE(uf); + EXPECT_TRUE(uf.IsTriviallyDestructible()); + int x = 12; + EXPECT_EQ(uf.Call(&x), 12); +} + +TEST(UntypedFunction, CallTrivialWithReference) { + auto uf = UntypedFunction::Create([](int& x) { x = 3; }); + EXPECT_TRUE(uf); + EXPECT_TRUE(uf.IsTriviallyDestructible()); + int x = 12; + uf.Call(x); + EXPECT_EQ(x, 3); +} + +TEST(UntypedFunction, CallTrivialWithRvalueReference) { + auto uf = UntypedFunction::Create([](int&& x) { return x - 2; }); + EXPECT_TRUE(uf); + EXPECT_TRUE(uf.IsTriviallyDestructible()); + EXPECT_EQ(uf.Call(34), 32); +} + +TEST(UntypedFunction, CallNontrivialWithInt) { + std::vector list; + auto uf = UntypedFunction::Create([list](int x) mutable { + list.push_back(x); + return list.size(); + }); + EXPECT_TRUE(uf); + EXPECT_FALSE(uf.IsTriviallyDestructible()); + EXPECT_EQ(uf.Call(17), 1); + EXPECT_EQ(uf.Call(17), 2); +} + +TEST(UntypedFunction, CallNontrivialWithPointer) { + std::vector list; + auto uf = UntypedFunction::Create([list](int* x) mutable { + list.push_back(*x); + return list.data(); + }); + EXPECT_TRUE(uf); + EXPECT_FALSE(uf.IsTriviallyDestructible()); + int x = 12; + EXPECT_THAT(uf.Call(&x), Pointee(12)); +} + +TEST(UntypedFunction, CallNontrivialWithReference) { + std::vector list = {34, 35, 36}; + auto uf = + UntypedFunction::Create([list](int& x) { x = list[1]; }); + EXPECT_TRUE(uf); + EXPECT_FALSE(uf.IsTriviallyDestructible()); + int x = 12; + uf.Call(x); + EXPECT_EQ(x, 35); +} + +TEST(UntypedFunction, CallNontrivialWithRvalueReference) { + std::vector list; + auto uf = UntypedFunction::Create([list](int&& x) mutable { + list.push_back(x); + return list.size(); + }); + EXPECT_TRUE(uf); + EXPECT_FALSE(uf.IsTriviallyDestructible()); + EXPECT_EQ(uf.Call(34), 1); + EXPECT_EQ(uf.Call(34), 2); +} + +int AddFive(int x) { + return x + 5; +} +int DereferencePointer(int* x) { + return *x; +} +void AssignThree(int& x) { + x = 3; +} +int SubtractTwo(int&& x) { + return x - 2; +} + +TEST(UntypedFunction, CallFunctionPointerWithInt) { + auto uf = UntypedFunction::Create(AddFive); + EXPECT_TRUE(uf); + EXPECT_TRUE(uf.IsTriviallyDestructible()); + EXPECT_EQ(uf.Call(17), 22); +} + +TEST(UntypedFunction, CallFunctionPointerWithPointer) { + auto uf = UntypedFunction::Create(DereferencePointer); + EXPECT_TRUE(uf); + EXPECT_TRUE(uf.IsTriviallyDestructible()); + int x = 12; + EXPECT_EQ(uf.Call(&x), 12); +} + +TEST(UntypedFunction, CallFunctionPointerWithReference) { + auto uf = UntypedFunction::Create(AssignThree); + EXPECT_TRUE(uf); + EXPECT_TRUE(uf.IsTriviallyDestructible()); + int x = 12; + uf.Call(x); + EXPECT_EQ(x, 3); +} + +TEST(UntypedFunction, CallFunctionPointerWithRvalueReference) { + auto uf = UntypedFunction::Create(SubtractTwo); + EXPECT_TRUE(uf); + EXPECT_TRUE(uf.IsTriviallyDestructible()); + EXPECT_EQ(uf.Call(34), 32); +} + +TEST(UntypedFunction, CallTrivialWithNoArgs) { + int arr[] = {1, 2, 3}; + auto uf = UntypedFunction::Create([arr] { return arr[1]; }); + EXPECT_TRUE(uf); + EXPECT_TRUE(uf.IsTriviallyDestructible()); + EXPECT_EQ(uf.Call(), 2); +} + +TEST(UntypedFunction, CallLargeTrivialWithNoArgs) { + int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0}; + auto uf = UntypedFunction::Create([arr] { return arr[4]; }); + EXPECT_TRUE(uf); + EXPECT_FALSE(uf.IsTriviallyDestructible()); + EXPECT_EQ(uf.Call(), 5); +} + +TEST(UntypedFunction, MoveonlyReturnValue) { + auto uf = UntypedFunction::Create()>( + [] { return std::make_unique(567); }); + EXPECT_THAT(uf.Call()>(), Pointee(567)); +} + +TEST(UntypedFunction, MoveonlyArgument) { + auto uf = UntypedFunction::Create)>( + [](std::unique_ptr x) { return *x + 19; }); + EXPECT_EQ(uf.Call)>(std::make_unique(40)), 59); +} + +TEST(UntypedFunction, MoveOnlyCallable) { + auto uf = UntypedFunction::Create( + [x = std::make_unique(17)] { return ++*x; }); + EXPECT_TRUE(uf); + EXPECT_FALSE(uf.IsTriviallyDestructible()); + EXPECT_EQ(uf.Call(), 18); + EXPECT_EQ(uf.Call(), 19); + UntypedFunction uf2 = std::move(uf); + EXPECT_TRUE(uf); + EXPECT_TRUE(uf.IsTriviallyDestructible()); + EXPECT_FALSE(uf2.IsTriviallyDestructible()); + EXPECT_EQ(uf.Call(), 20); + EXPECT_EQ(uf.Call(), 21); +} + +class Destroyer { + public: + explicit Destroyer(int& destroy_count) : destroy_count_(&destroy_count) {} + ~Destroyer() { ++*destroy_count_; } + int operator()() { return 72; } + int* destroy_count_; +}; + +TEST(UntypedFunction, CallableIsDestroyed) { + int destroy_count = 0; + { + auto uf = UntypedFunction::Create(Destroyer(destroy_count)); + // Destruction count is 1 here, because the temporary we created above was + // destroyed. + EXPECT_EQ(destroy_count, 1); + { + auto uf2 = std::move(uf); + EXPECT_EQ(destroy_count, 1); + } + // `uf2` was destroyed. + EXPECT_EQ(destroy_count, 2); + } + // `uf` was destroyed, but it didn't contain a Destroyer since we moved it to + // `uf2` above. + EXPECT_EQ(destroy_count, 2); +} + +TEST(UntypedFunction, MoveAssign) { + int destroy_count = 0; + auto uf = UntypedFunction::Create(Destroyer(destroy_count)); + EXPECT_TRUE(uf); + EXPECT_FALSE(uf.IsTriviallyDestructible()); + // Destruction count is 1 here, because the temporary we created above was + // destroyed. + EXPECT_EQ(destroy_count, 1); + UntypedFunction uf2 = nullptr; + EXPECT_FALSE(uf2); + EXPECT_TRUE(uf2.IsTriviallyDestructible()); + + uf2 = std::move(uf); + EXPECT_TRUE(uf.IsTriviallyDestructible()); + EXPECT_TRUE(uf2); + EXPECT_FALSE(uf2.IsTriviallyDestructible()); + EXPECT_EQ(destroy_count, 1); // The callable was not destroyed. + EXPECT_EQ(uf2.Call(), 72); + + UntypedFunction uf3 = nullptr; + uf2 = std::move(uf3); + EXPECT_FALSE(uf2); + EXPECT_TRUE(uf2.IsTriviallyDestructible()); + EXPECT_EQ(destroy_count, 2); // The callable was destroyed by the assignment. +} + +TEST(UntypedFunction, NullptrAssign) { + int destroy_count = 0; + auto uf = UntypedFunction::Create(Destroyer(destroy_count)); + EXPECT_TRUE(uf); + EXPECT_FALSE(uf.IsTriviallyDestructible()); + // Destruction count is 1 here, because the temporary we created above was + // destroyed. + EXPECT_EQ(destroy_count, 1); + + uf = nullptr; + EXPECT_FALSE(uf); + EXPECT_TRUE(uf.IsTriviallyDestructible()); + EXPECT_EQ(destroy_count, 2); // The callable was destroyed by the assignment. +} + +TEST(UntypedFunction, Swap) { + int x = 13; + auto uf = UntypedFunction::Create([x]() mutable { return ++x; }); + EXPECT_TRUE(uf); + EXPECT_TRUE(uf.IsTriviallyDestructible()); + auto y = std::make_unique(113); + auto uf2 = + UntypedFunction::Create([y = std::move(y)] { return ++*y; }); + EXPECT_TRUE(uf2); + EXPECT_FALSE(uf2.IsTriviallyDestructible()); + UntypedFunction uf3 = nullptr; + EXPECT_FALSE(uf3); + EXPECT_TRUE(uf3.IsTriviallyDestructible()); + + EXPECT_EQ(uf.Call(), 14); + swap(uf, uf2); + EXPECT_TRUE(uf); + EXPECT_FALSE(uf.IsTriviallyDestructible()); + EXPECT_TRUE(uf2); + EXPECT_TRUE(uf2.IsTriviallyDestructible()); + EXPECT_EQ(uf.Call(), 114); + EXPECT_EQ(uf2.Call(), 15); + + swap(uf, uf3); + EXPECT_FALSE(uf); + EXPECT_TRUE(uf.IsTriviallyDestructible()); + EXPECT_TRUE(uf3); + EXPECT_FALSE(uf3.IsTriviallyDestructible()); + EXPECT_EQ(uf3.Call(), 115); +} + +} // namespace +} // namespace webrtc