diff options
author | ADAM David Alan Martin <adam.martin@10gen.com> | 2017-03-30 15:11:03 -0400 |
---|---|---|
committer | ADAM David Alan Martin <adam.martin@10gen.com> | 2017-03-30 15:11:03 -0400 |
commit | c20914d6739c8474e80646e461b8f5121f9758cf (patch) | |
tree | 23715ed58d712356c12e5c576b4180a72a9182df /src/mongo/base/clonable_ptr_test.cpp | |
parent | 1fecd837ad6e7d0913a9e65789fbfb6c2c74304a (diff) | |
download | mongo-c20914d6739c8474e80646e461b8f5121f9758cf.tar.gz |
SERVER-26025 Smart pointer with clone on copy
This clonable pointer works like a unique_ptr, but upon
copy it invokes a customizable cloning function. A `clone`
member function is used by default. There exist several
customization points.
Diffstat (limited to 'src/mongo/base/clonable_ptr_test.cpp')
-rw-r--r-- | src/mongo/base/clonable_ptr_test.cpp | 974 |
1 files changed, 974 insertions, 0 deletions
diff --git a/src/mongo/base/clonable_ptr_test.cpp b/src/mongo/base/clonable_ptr_test.cpp new file mode 100644 index 00000000000..5554f1fbe6a --- /dev/null +++ b/src/mongo/base/clonable_ptr_test.cpp @@ -0,0 +1,974 @@ +// clonable_ptr_test.cpp + +/*- + * Copyright (C) 2016 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/base/clonable_ptr.h" + +#include "mongo/stdx/functional.h" +#include "mongo/stdx/memory.h" +#include "mongo/unittest/unittest.h" + +#include <boost/lexical_cast.hpp> + +namespace { +namespace stdx = mongo::stdx; + +// These testing helper classes model various kinds of class which should be compatible with +// `mongo::clonable_ptr`. The basic use cases satisfied by each class are described in each class's +// documentation. + +// This class models the `Clonable` concept, and is used to test the simple case of `clonable_ptr`. +class ClonableTest { +private: + std::string data = + "This is the string data which is stored to make Functor Clonable need a complicated copy " + "ctor."; + +public: + std::unique_ptr<ClonableTest> clone() const { + return mongo::stdx::make_unique<ClonableTest>(); + } +}; + +// This class provides a member structure which models `CloneFactory<AltClonableTest>`. The member +// structure is available under the expected member name of `clone_factory_type`. The +// `CloneFactory` is stateless. +class AltClonableTest { +private: + std::string data = + "This is the string data which is stored to make Functor Clonable need a complicated copy " + "ctor."; + +public: + struct clone_factory_type { + std::unique_ptr<AltClonableTest> operator()(const AltClonableTest&) const { + return mongo::stdx::make_unique<AltClonableTest>(); + } + }; +}; + +// This class requires a companion cloning function models `CloneFactory<Alt2ClonableTest>`. There +// is an attendant specialization of the `mongo::clonable_traits` metafunction to provide the clone +// factory for this type. That `CloneFactory` is stateless. +class Alt2ClonableTest { +private: + std::string data = + "This is the string data which is stored to make Functor Clonable need a complicated copy " + "ctor."; +}; +} // namespace + +namespace mongo { +// This specialization of the `mongo::clonable_traits` metafunction provides a model of a stateless +// `CloneFactory<Alt2ClonableTest>` +template <> +struct clonable_traits<::Alt2ClonableTest> { + struct clone_factory_type { + std::unique_ptr<Alt2ClonableTest> operator()(const Alt2ClonableTest&) const { + return mongo::stdx::make_unique<Alt2ClonableTest>(); + } + }; +}; +} // namespace mongo + +namespace { +// This class uses a stateful cloning function provided by the `getCloningFunction` static member. +// This stateful `CloneFactory<FunctorClonable>` must be passed to constructors of the +// `cloning_ptr`. +class FunctorClonable { +private: + std::string data = + "This is the string data which is stored to make Functor Clonable need a complicated copy " + "ctor."; + +public: + using CloningFunctionType = + mongo::stdx::function<std::unique_ptr<FunctorClonable>(const FunctorClonable&)>; + + static CloningFunctionType getCloningFunction() { + return + [](const FunctorClonable& c) { return mongo::stdx::make_unique<FunctorClonable>(c); }; + } +}; + + +// This class uses a stateful cloning function provided by the `getCloningFunction` static member. +// This stateful `CloneFactory<FunctorWithDynamicStateClonable>` must be passed to constructors of +// the `cloning_ptr`. The `CloneFactory` for this type dynamically updates its internal state. +// This is used to test cloning of objects that have dynamically changing clone factories. +class FunctorWithDynamicStateClonable { +private: + std::string data = + "This is the string data which is stored to make Functor Clonable need a complicated copy " + "ctor."; + +public: + FunctorWithDynamicStateClonable(const FunctorWithDynamicStateClonable&) = delete; + FunctorWithDynamicStateClonable() = default; + + FunctorWithDynamicStateClonable(const std::string& s) : data(s) {} + + using CloningFunctionType = stdx::function<std::unique_ptr<FunctorWithDynamicStateClonable>( + const FunctorWithDynamicStateClonable&)>; + + static CloningFunctionType getCloningFunction() { + return [calls = 0](const FunctorWithDynamicStateClonable& c) mutable { + return mongo::stdx::make_unique<FunctorWithDynamicStateClonable>( + c.data + boost::lexical_cast<std::string>(++calls)); + }; + } +}; + +// This class models `Clonable`, with a return from clone which is +// `Constructible<std::unique_ptr<RawPointerClonable>>` but isn't +// `std::unique_ptr<RawPointerClonable>`. This is used to test that the `clonable_ptr` class does +// not expect `RawPointerClonable::clone() const` to return a model of +// `UniquePtr<RawPointerClonable>` +class RawPointerClonable { +public: + RawPointerClonable* clone() const { + return new RawPointerClonable; + } +}; + +// This class models `Clonable`, with a return from clone which is +// `Constructible<std::unique_ptr<UniquePtrClonable>>` because it is +// `std::unique_ptr<UniquePtrClonable>`. This is used to test that the `clonable_ptr` class can +// use a `UniquePtrClonable::clone() const` that returns a model of +// `UniquePtr<UniquePtrClonable>` +class UniquePtrClonable { +public: + std::unique_ptr<UniquePtrClonable> clone() const { + return mongo::stdx::make_unique<UniquePtrClonable>(); + } +}; + +TEST(ClonablePtrTest, syntax_smoke_test) { +// TODO: Either add a compressed pair type for optimization, or wait for MSVC to get this feature by +// default. MSVC doesn't make its tuple compressed, which causes this test to fail on MSVC. +#ifndef _MSC_VER + { + mongo::clonable_ptr<ClonableTest> p; + + p = mongo::stdx::make_unique<ClonableTest>(); + + mongo::clonable_ptr<ClonableTest> p2 = p; + + ASSERT_TRUE(p != p2); + + static_assert(sizeof(p) == sizeof(ClonableTest*), + "`mongo::clonable_ptr< T >` should be `sizeof` a pointer when there is no " + "CloneFactory"); + } +#endif + + { + mongo::clonable_ptr<AltClonableTest> p; + + p = mongo::stdx::make_unique<AltClonableTest>(); + + mongo::clonable_ptr<AltClonableTest> p2 = p; + + ASSERT_TRUE(p != p2); + } + + { + mongo::clonable_ptr<Alt2ClonableTest> p; + + p = mongo::stdx::make_unique<Alt2ClonableTest>(); + + mongo::clonable_ptr<Alt2ClonableTest> p2 = p; + + ASSERT_TRUE(p != p2); + } + + { + mongo::clonable_ptr<FunctorClonable, FunctorClonable::CloningFunctionType> p{ + FunctorClonable::getCloningFunction()}; + + p = mongo::stdx::make_unique<FunctorClonable>(); + + mongo::clonable_ptr<FunctorClonable, FunctorClonable::CloningFunctionType> p2 = p; + + ASSERT_TRUE(p != p2); + + mongo::clonable_ptr<FunctorClonable, FunctorClonable::CloningFunctionType> p3{ + FunctorClonable::getCloningFunction()}; + + auto tmp = mongo::stdx::make_unique<FunctorClonable>(); + p3 = std::move(tmp); + + ASSERT_TRUE(p != p2); + ASSERT_TRUE(p2 != p3); + ASSERT_TRUE(p != p3); + } +} + +// These tests check that all expected valid syntactic forms of use for the +// `mongo::clonable_ptr<Clonable>` are valid. These tests assert nothing but provide a single +// unified place to check the syntax of this component. Build failures in these parts indicate that +// a change to the component has broken an expected valid syntactic usage. Any expected valid usage +// which is not in this list should be added. +namespace SyntaxTests { +template <typename Clonable> +void construction() { + // Test default construction + { mongo::clonable_ptr<Clonable>{}; } + + // Test construction from a nullptr + { mongo::clonable_ptr<Clonable>{nullptr}; } + + // Test construction from a Clonable pointer. + { + Clonable* const local = nullptr; + mongo::clonable_ptr<Clonable>{local}; + } + + // Test move construction. + { mongo::clonable_ptr<Clonable>{mongo::clonable_ptr<Clonable>{}}; } + + // Test copy construction. + { + mongo::clonable_ptr<Clonable> a; + mongo::clonable_ptr<Clonable> b{a}; + } + + // Test move assignment. + { + mongo::clonable_ptr<Clonable> a; + a = mongo::clonable_ptr<Clonable>{}; + } + + // Test copy assignment. + { + mongo::clonable_ptr<Clonable> a; + mongo::clonable_ptr<Clonable> b; + b = a; + } + + // Test unique pointer construction + { mongo::clonable_ptr<Clonable>{mongo::stdx::make_unique<Clonable>()}; } + + // Test unique pointer construction (conversion) + { + auto acceptor = [](const mongo::clonable_ptr<Clonable>&) {}; + acceptor(mongo::stdx::make_unique<Clonable>()); + } + + // Test non-conversion pointer construction + { static_assert(!std::is_convertible<Clonable*, mongo::clonable_ptr<Clonable>>::value, ""); } + + // Test conversion unique pointer construction + { + static_assert( + std::is_convertible<std::unique_ptr<Clonable>, mongo::clonable_ptr<Clonable>>::value, + ""); + } +} + +// Tests that syntactic forms that require augmented construction are proper +template <typename Clonable, typename CloneFactory> +void augmentedConstruction() { + // Test default construction + { + static_assert( + !std::is_default_constructible<mongo::clonable_ptr<Clonable, CloneFactory>>::value, ""); + } + + // Test Clone Factory construction + { mongo::clonable_ptr<Clonable, CloneFactory>{Clonable::getCloningFunction()}; } + +// TODO: Revist this when MSVC's enable-if and deletion on ctors works. +#ifndef _MSC_VER + // Test non-construction from a nullptr + { + static_assert(!std::is_constructible<mongo::clonable_ptr<Clonable, CloneFactory>, + std::nullptr_t>::value, + ""); + } +#endif + + // Test construction from a nullptr with factory + { mongo::clonable_ptr<Clonable, CloneFactory>{nullptr, Clonable::getCloningFunction()}; } + +// TODO: Revist this when MSVC's enable-if and deletion on ctors works. +#ifndef _MSC_VER + // Test construction from a raw Clonable pointer. + { + static_assert( + !std::is_constructible<mongo::clonable_ptr<Clonable, CloneFactory>, Clonable*>::value, + ""); + } +#endif + + + // Test initialization of a raw Clonable pointer with factory, using reset. + { + Clonable* const local = nullptr; + mongo::clonable_ptr<Clonable, CloneFactory> p{Clonable::getCloningFunction()}; + p.reset(local); + } + + // Test move construction. + { + mongo::clonable_ptr<Clonable, CloneFactory>{ + mongo::clonable_ptr<Clonable, CloneFactory>{Clonable::getCloningFunction()}}; + } + + // Test copy construction. + { + mongo::clonable_ptr<Clonable, CloneFactory> a{Clonable::getCloningFunction()}; + mongo::clonable_ptr<Clonable, CloneFactory> b{a}; + } + + // Test augmented copy construction. + { + mongo::clonable_ptr<Clonable, CloneFactory> a{Clonable::getCloningFunction()}; + mongo::clonable_ptr<Clonable, CloneFactory> b{a, Clonable::getCloningFunction()}; + } + + + // Test move assignment. + { + mongo::clonable_ptr<Clonable, CloneFactory> a{Clonable::getCloningFunction()}; + a = mongo::clonable_ptr<Clonable, CloneFactory>{Clonable::getCloningFunction()}; + } + + // Test copy assignment. + { + mongo::clonable_ptr<Clonable, CloneFactory> a{Clonable::getCloningFunction()}; + mongo::clonable_ptr<Clonable, CloneFactory> b{Clonable::getCloningFunction()}; + b = a; + } + + // Test unique pointer construction + { + mongo::clonable_ptr<Clonable, CloneFactory>{mongo::stdx::make_unique<Clonable>(), + Clonable::getCloningFunction()}; + } + + // Test augmented unique pointer construction + { + mongo::clonable_ptr<Clonable, CloneFactory>{mongo::stdx::make_unique<Clonable>(), + Clonable::getCloningFunction()}; + } + + // Test non-conversion pointer construction + { + static_assert( + !std::is_convertible<mongo::clonable_ptr<Clonable, CloneFactory>, Clonable*>::value, + ""); + } + + // Test non-conversion from factory + { + static_assert( + !std::is_convertible<mongo::clonable_ptr<Clonable, CloneFactory>, CloneFactory>::value, + ""); + } + + // Test conversion unique pointer construction + { + static_assert(!std::is_convertible<std::unique_ptr<Clonable>, + mongo::clonable_ptr<Clonable, CloneFactory>>::value, + ""); + } +} + +template <typename Clonable> +void pointerOperations() { + mongo::clonable_ptr<Clonable> a; + + // Test `.get()` functionality: + { + Clonable* p = a.get(); + (void)p; + } + + // Test `->` functionality + { + // We don't actually want to call the dtor, but we want the compiler to check that we + // have. + if (false) { + a->~Clonable(); + } + } + + // Test `*` functionality + { + Clonable& r = *a; + (void)r; + } + + // Test reset functionality + { + a.reset(); + a.reset(nullptr); + a.reset(new Clonable); + } +} + +template <typename Clonable> +void equalityOperations() { + mongo::clonable_ptr<Clonable> a; + mongo::clonable_ptr<Clonable> b; + + std::unique_ptr<Clonable> ua; + + // Test equality expressions + { + (void)(a == a); + (void)(a == b); + (void)(b == a); + + (void)(a == ua); + (void)(ua == a); + + (void)(nullptr == a); + (void)(a == nullptr); + } + + // Test inequality expressions + { + (void)(a != a); + (void)(a != b); + (void)(b != a); + + (void)(a != ua); + (void)(ua != a); + + (void)(nullptr == a); + (void)(a == nullptr); + } +} +} // namespace SyntaxTests + +TEST(ClonablePtrSyntaxTests, construction) { + SyntaxTests::construction<ClonableTest>(); + SyntaxTests::construction<AltClonableTest>(); + SyntaxTests::construction<Alt2ClonableTest>(); + SyntaxTests::construction<RawPointerClonable>(); + SyntaxTests::construction<UniquePtrClonable>(); +} + +TEST(ClonablePtrSyntaxTests, augmentedConstruction) { + SyntaxTests::augmentedConstruction<FunctorClonable, FunctorClonable::CloningFunctionType>(); + SyntaxTests::augmentedConstruction<FunctorWithDynamicStateClonable, + FunctorWithDynamicStateClonable::CloningFunctionType>(); +} + +TEST(ClonablePtrSyntaxTests, pointerOperations) { + SyntaxTests::pointerOperations<ClonableTest>(); + SyntaxTests::pointerOperations<AltClonableTest>(); + SyntaxTests::pointerOperations<Alt2ClonableTest>(); + SyntaxTests::pointerOperations<RawPointerClonable>(); + SyntaxTests::pointerOperations<UniquePtrClonable>(); +} + +TEST(ClonablePtrSyntaxTests, equalityOperations) { + SyntaxTests::equalityOperations<ClonableTest>(); + SyntaxTests::equalityOperations<AltClonableTest>(); + SyntaxTests::equalityOperations<Alt2ClonableTest>(); + SyntaxTests::equalityOperations<RawPointerClonable>(); + SyntaxTests::equalityOperations<UniquePtrClonable>(); +} + +namespace BehaviorTests { +class DetectDestruction { +public: + static int activeCount; + + static void resetCount() { + activeCount = 0; + } + + ~DetectDestruction() { + --activeCount; + } + + DetectDestruction(const DetectDestruction&) { + ++activeCount; + } + + DetectDestruction& operator=(const DetectDestruction&) = delete; + + DetectDestruction(DetectDestruction&&) = delete; + DetectDestruction& operator=(DetectDestruction&&) = delete; + + DetectDestruction() { + ++activeCount; + } + + std::unique_ptr<DetectDestruction> clone() const { + return mongo::stdx::make_unique<DetectDestruction>(*this); + } +}; + +int DetectDestruction::activeCount = 0; + +struct DestructionGuard { + DestructionGuard(const DestructionGuard&) = delete; + DestructionGuard& operator=(const DestructionGuard&) = delete; + + DestructionGuard() { + DetectDestruction::resetCount(); + } + + ~DestructionGuard() { + ASSERT_EQ(DetectDestruction::activeCount, 0); + } +}; + +TEST(ClonablePtrTest, basic_construction_test) { + // Do not default construct the object + { + DestructionGuard check; + mongo::clonable_ptr<DetectDestruction> p; + ASSERT_EQ(DetectDestruction::activeCount, 0); + } + + // Do not make unnecessary copies of the object from ptr + { + DestructionGuard check; + mongo::clonable_ptr<DetectDestruction> p{new DetectDestruction}; + ASSERT_EQ(DetectDestruction::activeCount, 1); + } + + // Do not make unnecessary copies of the object from unique_ptr + { + DestructionGuard check; + mongo::clonable_ptr<DetectDestruction> p{mongo::stdx::make_unique<DetectDestruction>()}; + ASSERT_EQ(DetectDestruction::activeCount, 1); + } + { + DestructionGuard check; + mongo::clonable_ptr<DetectDestruction> p = mongo::stdx::make_unique<DetectDestruction>(); + ASSERT_EQ(DetectDestruction::activeCount, 1); + } + + // Two separate constructions are unlinked + { + DestructionGuard check; + + mongo::clonable_ptr<DetectDestruction> p1{mongo::stdx::make_unique<DetectDestruction>()}; + ASSERT_EQ(DetectDestruction::activeCount, 1); + + { + mongo::clonable_ptr<DetectDestruction> p2{ + mongo::stdx::make_unique<DetectDestruction>()}; + ASSERT_EQ(DetectDestruction::activeCount, 2); + } + ASSERT_EQ(DetectDestruction::activeCount, 1); + } + + // Two separate constructions can have opposite order and be unlinked + { + DestructionGuard check; + + auto p1 = mongo::stdx::make_unique<mongo::clonable_ptr<DetectDestruction>>( + mongo::stdx::make_unique<DetectDestruction>()); + ASSERT_EQ(DetectDestruction::activeCount, 1); + + auto p2 = mongo::stdx::make_unique<mongo::clonable_ptr<DetectDestruction>>( + mongo::stdx::make_unique<DetectDestruction>()); + ASSERT_EQ(DetectDestruction::activeCount, 2); + + p1.reset(); + ASSERT_EQ(DetectDestruction::activeCount, 1); + + p2.reset(); + ASSERT_EQ(DetectDestruction::activeCount, 0); + } +} + +// TODO: Bring in an "equivalence class for equality predicate testing" framework. +// Equals and Not Equals need to be tested independently -- It is not valid to assume that equals +// and not equals are correctly implemented as complimentary predicates. Equality must be +// reflexive, symmetric and transitive. This requres several instances that all have the same +// value. Simply testing "2 == 2" and "3 != 2" is insufficient. Every combination of position and +// equality must be tested to come out as expected. +// +// Consider that with equality it is important to make sure that `a == b` has the same meaning as `b +// == a`. It is also necessary to check that `a == b` and `b == c` and `a == c` is true, when all +// three are equal, and to do so in reverse: `b == a` and `c == b` and `c == a`. Further, the +// relationships above have to hold for multiple cases. Similar cases need to be tested for +// inequality. +// +// Further, equality is an incredibly important operation to test completely and thoroughly -- +// besides being a critical element in code using any value modeling type, it also is the keystone +// in any testing schedule for a copyable and movable value type. Almost all testing of behavior +// relies upon being able to detect fundamental differences in value. In order to provide this +// correctly, we provide a full battery of tests for equality in all mathematically relevant +// situations. For this equality testing schedule to be correct, we require a mechanism to +// initialize objects (and references to those objects) which have predictable value. These +// predictable values are then used to test known equality expressions for the correct evaluation. +// +// All other tests can then just use equality to verify that an object is in the desired state. +// This greatly simplifies testing and also makes tests more precise. +TEST(ClonablePtrTest, basicEqualityTest) { + DestructionGuard check; + + mongo::clonable_ptr<DetectDestruction> n1; + mongo::clonable_ptr<DetectDestruction> n2; + mongo::clonable_ptr<DetectDestruction> n3; + + mongo::clonable_ptr<DetectDestruction> a = mongo::stdx::make_unique<DetectDestruction>(); + mongo::clonable_ptr<DetectDestruction> b = mongo::stdx::make_unique<DetectDestruction>(); + mongo::clonable_ptr<DetectDestruction> c = mongo::stdx::make_unique<DetectDestruction>(); + + const mongo::clonable_ptr<DetectDestruction>& ap = a; + const mongo::clonable_ptr<DetectDestruction>& bp = b; + const mongo::clonable_ptr<DetectDestruction>& cp = c; + const mongo::clonable_ptr<DetectDestruction>& ap2 = a; + const mongo::clonable_ptr<DetectDestruction>& bp2 = b; + const mongo::clonable_ptr<DetectDestruction>& cp2 = c; + + // Equals operator + + // Identity checks + + ASSERT(n1 == n1); + ASSERT(n2 == n2); + ASSERT(n3 == n3); + + ASSERT(a == a); + ASSERT(b == b); + ASSERT(c == c); + + // Same value checks. (Because unique pointers should never be the same value, we have to use + // references.) + + ASSERT(n1 == n2); + ASSERT(n1 == n3); + + ASSERT(n2 == n1); + ASSERT(n2 == n3); + + ASSERT(n3 == n1); + ASSERT(n3 == n2); + + ASSERT(a == ap); + ASSERT(a == ap2); + ASSERT(ap == a); + ASSERT(ap == ap2); + ASSERT(ap2 == a); + ASSERT(ap2 == ap); + + ASSERT(b == bp); + ASSERT(b == bp2); + ASSERT(bp == b); + ASSERT(bp == bp2); + ASSERT(bp2 == b); + ASSERT(bp2 == bp); + + ASSERT(c == cp); + ASSERT(c == cp2); + ASSERT(cp == c); + ASSERT(cp == cp2); + ASSERT(cp2 == c); + ASSERT(cp2 == cp); + + // Different value checks: + + ASSERT(!(a == n1)); + ASSERT(!(b == n1)); + ASSERT(!(c == n1)); + ASSERT(!(a == n2)); + ASSERT(!(b == n2)); + ASSERT(!(c == n2)); + + ASSERT(!(n1 == a)); + ASSERT(!(n1 == b)); + ASSERT(!(n1 == c)); + ASSERT(!(n2 == a)); + ASSERT(!(n2 == b)); + ASSERT(!(n2 == c)); + + ASSERT(!(a == b)); + ASSERT(!(a == c)); + + ASSERT(!(b == a)); + ASSERT(!(b == c)); + + ASSERT(!(c == a)); + ASSERT(!(c == b)); + + // Not Equals operator + + // Identity checks + + ASSERT(!(n1 != n1)); + ASSERT(!(n2 != n2)); + ASSERT(!(n3 != n3)); + + ASSERT(!(a != a)); + ASSERT(!(b != b)); + ASSERT(!(c != c)); + + // Same value checks. (Because unique pointers should never be the same value, we have to use + // references.) + + ASSERT(!(n1 != n2)); + ASSERT(!(n1 != n3)); + + ASSERT(!(n2 != n1)); + ASSERT(!(n2 != n3)); + + ASSERT(!(n3 != n1)); + ASSERT(!(n3 != n2)); + + ASSERT(!(a != ap)); + ASSERT(!(a != ap2)); + ASSERT(!(ap != a)); + ASSERT(!(ap != ap2)); + ASSERT(!(ap2 != a)); + ASSERT(!(ap2 != ap)); + + ASSERT(!(b != bp)); + ASSERT(!(b != bp2)); + ASSERT(!(bp != b)); + ASSERT(!(bp != bp2)); + ASSERT(!(bp2 != b)); + ASSERT(!(bp2 != bp)); + + ASSERT(!(c != cp)); + ASSERT(!(c != cp2)); + ASSERT(!(cp != c)); + ASSERT(!(cp != cp2)); + ASSERT(!(cp2 != c)); + ASSERT(!(cp2 != cp)); + + // Different value checks: + + ASSERT(a != n1); + ASSERT(b != n1); + ASSERT(c != n1); + ASSERT(a != n2); + ASSERT(b != n2); + ASSERT(c != n2); + + ASSERT(n1 != a); + ASSERT(n1 != b); + ASSERT(n1 != c); + ASSERT(n2 != a); + ASSERT(n2 != b); + ASSERT(n2 != c); + + ASSERT(a != b); + ASSERT(a != c); + + ASSERT(b != a); + ASSERT(b != c); + + ASSERT(c != a); + ASSERT(c != b); +} + +// TODO: all other forms of equality with other types (`std::nullptr_t` and `std::unique_ptr< T >`) +// need testing still. + +TEST(ClonablePtrTest, ownershipStabilityTest) { + { + DestructionGuard check; + + auto ptr_init = mongo::stdx::make_unique<DetectDestruction>(); + const auto* rp = ptr_init.get(); + + mongo::clonable_ptr<DetectDestruction> cp = std::move(ptr_init); + + ASSERT(rp == cp.get()); + + mongo::clonable_ptr<DetectDestruction> cp2 = std::move(cp); + + ASSERT(rp == cp2.get()); + + mongo::clonable_ptr<DetectDestruction> cp3; + + ASSERT(nullptr == cp3); + + cp3 = std::move(cp2); + + ASSERT(rp == cp3.get()); + } + + { + auto ptr_init = mongo::stdx::make_unique<DetectDestruction>(); + const auto* rp = ptr_init.get(); + + mongo::clonable_ptr<DetectDestruction> cp{ptr_init.release()}; + + ASSERT(rp == cp.get()); + + mongo::clonable_ptr<DetectDestruction> cp2 = std::move(cp); + + ASSERT(rp == cp2.get()); + + mongo::clonable_ptr<DetectDestruction> cp3; + + ASSERT(nullptr == cp3.get()); + + cp3 = std::move(cp2); + + ASSERT(rp == cp3.get()); + } +} + +class ClonableObject { +private: + int value = 0; + +public: + // ClonableObject( const ClonableObject & ) { abort(); } + ClonableObject() = default; + explicit ClonableObject(const int v) : value(v) {} + + std::unique_ptr<ClonableObject> clone() const { + return mongo::stdx::make_unique<ClonableObject>(*this); + } + + auto make_equality_lens() const -> decltype(std::tie(this->value)) { + return std::tie(value); + } +}; + +bool operator==(const ClonableObject& lhs, const ClonableObject& rhs) { + return lhs.make_equality_lens() == rhs.make_equality_lens(); +} + +bool operator!=(const ClonableObject& lhs, const ClonableObject& rhs) { + return !(lhs == rhs); +} + +TEST(ClonablePtrTest, noObjectCopySemanticTest) { + mongo::clonable_ptr<ClonableObject> p; + + mongo::clonable_ptr<ClonableObject> p2 = p; + ASSERT(p == p2); + + mongo::clonable_ptr<ClonableObject> p3; + + p3 = p; + ASSERT(p == p3); +} + +TEST(ClonablePtrTest, objectCopySemanticTest) { + mongo::clonable_ptr<ClonableObject> p = mongo::stdx::make_unique<ClonableObject>(1); + mongo::clonable_ptr<ClonableObject> q = mongo::stdx::make_unique<ClonableObject>(2); + ASSERT(p != q); + ASSERT(*p != *q); + + mongo::clonable_ptr<ClonableObject> p2 = p; + ASSERT(p != p2); + ASSERT(*p == *p2); + + mongo::clonable_ptr<ClonableObject> q2 = q; + ASSERT(q2 != q); + ASSERT(q2 != p); + ASSERT(q2 != p2); + ASSERT(*q2 == *q); + + q2 = p2; + ASSERT(q2 != q); + ASSERT(q2 != p); + ASSERT(q2 != p2); + ASSERT(*q2 == *p2); +} + +class Interface { +public: + virtual ~Interface() = default; + virtual void consumeText(const std::string& message) = 0; + virtual std::string produceText() = 0; + + std::unique_ptr<Interface> clone() const { + return std::unique_ptr<Interface>{this->clone_impl()}; + } + +private: + virtual Interface* clone_impl() const = 0; +}; + +class GeneratorImplementation : public Interface { +private: + const std::string root; + int generation = 0; + + GeneratorImplementation* clone_impl() const { + return new GeneratorImplementation{*this}; + } + +public: + explicit GeneratorImplementation(const std::string& m) : root(m) {} + + void consumeText(const std::string&) override {} + + std::string produceText() override { + return root + boost::lexical_cast<std::string>(++generation); + } +}; + +class StorageImplementation : public Interface { +private: + std::string store; + + StorageImplementation* clone_impl() const { + return new StorageImplementation{*this}; + } + +public: + void consumeText(const std::string& m) override { + store = m; + } + std::string produceText() override { + return store; + } +}; + + +TEST(ClonablePtrSimpleTest, simpleUsageExample) { + mongo::clonable_ptr<Interface> source; + mongo::clonable_ptr<Interface> sink; + + mongo::clonable_ptr<Interface> instance = stdx::make_unique<StorageImplementation>(); + + sink = instance; + + ASSERT(instance.get() != sink.get()); + + instance = stdx::make_unique<GeneratorImplementation>("base message"); + + + source = std::move(instance); + + + sink->consumeText(source->produceText()); +} + +} // namespace BehaviorTests +} // namespace |