summaryrefslogtreecommitdiff
path: root/src/mongo/base/clonable_ptr_test.cpp
diff options
context:
space:
mode:
authorADAM David Alan Martin <adam.martin@10gen.com>2017-03-30 15:11:03 -0400
committerADAM David Alan Martin <adam.martin@10gen.com>2017-03-30 15:11:03 -0400
commitc20914d6739c8474e80646e461b8f5121f9758cf (patch)
tree23715ed58d712356c12e5c576b4180a72a9182df /src/mongo/base/clonable_ptr_test.cpp
parent1fecd837ad6e7d0913a9e65789fbfb6c2c74304a (diff)
downloadmongo-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.cpp974
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