diff --git a/sdk/BUILD.gn b/sdk/BUILD.gn index 0948ce6574..e24ae4acdc 100644 --- a/sdk/BUILD.gn +++ b/sdk/BUILD.gn @@ -524,6 +524,7 @@ if (is_ios || is_mac) { "objc/Framework/UnitTests/avformatmappertests.mm", "objc/Framework/UnitTests/objc_video_decoder_factory_tests.mm", "objc/Framework/UnitTests/objc_video_encoder_factory_tests.mm", + "objc/Framework/UnitTests/scoped_cftyperef_tests.mm", ] if (is_ios && !(use_ios_simulator && @@ -540,6 +541,7 @@ if (is_ios || is_mac) { defines = [ "GTEST_RELATIVE_PATH" ] deps = [ + ":common_objc", ":peerconnection_objc", ":peerconnectionfactory_objc", ":videotoolbox_objc", @@ -549,6 +551,7 @@ if (is_ios || is_mac) { "../modules:module_api", "../rtc_base:rtc_base_tests_utils", "../system_wrappers:system_wrappers_default", + "//test:test_support", "//third_party/ocmock", ] diff --git a/sdk/objc/Framework/Classes/Common/scoped_cftyperef.h b/sdk/objc/Framework/Classes/Common/scoped_cftyperef.h index cde7a9069c..c54b03903c 100644 --- a/sdk/objc/Framework/Classes/Common/scoped_cftyperef.h +++ b/sdk/objc/Framework/Classes/Common/scoped_cftyperef.h @@ -15,25 +15,41 @@ #include namespace rtc { -template -class ScopedCFTypeRef { - public: - // RETAIN: ScopedCFTypeRef should retain the object when it takes - // ownership. - // ASSUME: Assume the object already has already been retained. - // ScopedCFTypeRef takes over ownership. - enum class RetainPolicy { RETAIN, ASSUME }; +// RETAIN: ScopedTypeRef should retain the object when it takes +// ownership. +// ASSUME: Assume the object already has already been retained. +// ScopedTypeRef takes over ownership. +enum class RetainPolicy { RETAIN, ASSUME }; - ScopedCFTypeRef() : ptr_(nullptr) {} - explicit ScopedCFTypeRef(T ptr) : ptr_(ptr) {} - ScopedCFTypeRef(T ptr, RetainPolicy policy) : ScopedCFTypeRef(ptr) { +namespace internal { +template +struct CFTypeRefTraits { + static T InvalidValue() { return nullptr; } + static void Release(T ref) { CFRelease(ref); } + static T Retain(T ref) { + CFRetain(ref); + return ref; + } +}; + +template +class ScopedTypeRef { + public: + ScopedTypeRef() : ptr_(Traits::InvalidValue()) {} + explicit ScopedTypeRef(T ptr) : ptr_(ptr) {} + ScopedTypeRef(T ptr, RetainPolicy policy) : ScopedTypeRef(ptr) { if (ptr_ && policy == RetainPolicy::RETAIN) - CFRetain(ptr_); + Traits::Retain(ptr_); } - ~ScopedCFTypeRef() { + ScopedTypeRef(const ScopedTypeRef& rhs) : ptr_(rhs.ptr_) { + if (ptr_) + ptr_ = Traits::Retain(ptr_); + } + + ~ScopedTypeRef() { if (ptr_) { - CFRelease(ptr_); + Traits::Release(ptr_); } } @@ -43,14 +59,14 @@ class ScopedCFTypeRef { bool operator!() const { return !ptr_; } - ScopedCFTypeRef& operator=(const T& rhs) { + ScopedTypeRef& operator=(const T& rhs) { if (ptr_) - CFRelease(ptr_); + Traits::Release(ptr_); ptr_ = rhs; return *this; } - ScopedCFTypeRef& operator=(const ScopedCFTypeRef& rhs) { + ScopedTypeRef& operator=(const ScopedTypeRef& rhs) { reset(rhs.get(), RetainPolicy::RETAIN); return *this; } @@ -64,21 +80,26 @@ class ScopedCFTypeRef { void reset(T ptr, RetainPolicy policy = RetainPolicy::ASSUME) { if (ptr && policy == RetainPolicy::RETAIN) - CFRetain(ptr); + Traits::Retain(ptr); if (ptr_) - CFRelease(ptr_); + Traits::Release(ptr_); ptr_ = ptr; } T release() { T temp = ptr_; - ptr_ = nullptr; + ptr_ = Traits::InvalidValue(); return temp; } private: T ptr_; }; +} // namespace internal + +template +using ScopedCFTypeRef = + internal::ScopedTypeRef>; template static ScopedCFTypeRef AdoptCF(T cftype) { diff --git a/sdk/objc/Framework/UnitTests/scoped_cftyperef_tests.mm b/sdk/objc/Framework/UnitTests/scoped_cftyperef_tests.mm new file mode 100644 index 0000000000..2832884884 --- /dev/null +++ b/sdk/objc/Framework/UnitTests/scoped_cftyperef_tests.mm @@ -0,0 +1,104 @@ +/* + * Copyright 2017 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 "sdk/objc/Framework/Classes/Common/scoped_cftyperef.h" + +#include "test/gtest.h" + +namespace { +struct TestType { + TestType() : has_value(true) {} + TestType(bool b) : has_value(b) {} + operator bool() { return has_value; } + bool has_value; + int retain_count = 0; +}; + +typedef TestType* TestTypeRef; + +struct TestTypeTraits { + static TestTypeRef InvalidValue() { return TestTypeRef(false); } + static void Release(TestTypeRef t) { t->retain_count--; } + static TestTypeRef Retain(TestTypeRef t) { + t->retain_count++; + return t; + } +}; +} // namespace + +using ScopedTestType = rtc::internal::ScopedTypeRef; + +// In these tests we sometime introduce variables just to +// observe side-effects. Ignore the compilers complaints. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-variable" + +TEST(ScopedTypeRefTest, ShouldNotRetainByDefault) { + TestType a; + ScopedTestType ref(&a); + EXPECT_EQ(0, a.retain_count); +} + +TEST(ScopedTypeRefTest, ShouldRetainWithPolicy) { + TestType a; + ScopedTestType ref(&a, rtc::RetainPolicy::RETAIN); + EXPECT_EQ(1, a.retain_count); +} + +TEST(ScopedTypeRefTest, ShouldReleaseWhenLeavingScope) { + TestType a; + EXPECT_EQ(0, a.retain_count); + { + ScopedTestType ref(&a, rtc::RetainPolicy::RETAIN); + EXPECT_EQ(1, a.retain_count); + } + EXPECT_EQ(0, a.retain_count); +} + +TEST(ScopedTypeRefTest, ShouldBeCopyable) { + TestType a; + EXPECT_EQ(0, a.retain_count); + { + ScopedTestType ref1(&a, rtc::RetainPolicy::RETAIN); + EXPECT_EQ(1, a.retain_count); + ScopedTestType ref2 = ref1; + EXPECT_EQ(2, a.retain_count); + } + EXPECT_EQ(0, a.retain_count); +} + +TEST(ScopedTypeRefTest, CanReleaseOwnership) { + TestType a; + EXPECT_EQ(0, a.retain_count); + { + ScopedTestType ref(&a, rtc::RetainPolicy::RETAIN); + EXPECT_EQ(1, a.retain_count); + TestTypeRef b = ref.release(); + } + EXPECT_EQ(1, a.retain_count); +} + +TEST(ScopedTypeRefTest, ShouldBeTestableForTruthiness) { + ScopedTestType ref; + EXPECT_FALSE(ref); + TestType a; + ref = &a; + EXPECT_TRUE(ref); + ref.release(); + EXPECT_FALSE(ref); +} + +TEST(ScopedTypeRefTest, ShouldProvideAccessToWrappedType) { + TestType a; + ScopedTestType ref(&a); + EXPECT_EQ(&(a.retain_count), &(ref->retain_count)); +} + +#pragma clang diagnostic pop