mirror of https://github.com/mongodb/mongo
991 lines
29 KiB
C++
991 lines
29 KiB
C++
/**
|
|
* Copyright (C) 2018-present MongoDB, Inc.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the Server Side Public License, version 1,
|
|
* as published by MongoDB, Inc.
|
|
*
|
|
* 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
|
|
* Server Side Public License for more details.
|
|
*
|
|
* You should have received a copy of the Server Side Public License
|
|
* along with this program. If not, see
|
|
* <http://www.mongodb.com/licensing/server-side-public-license>.
|
|
*
|
|
* 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 Server Side 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/base/string_data.h"
|
|
#include "mongo/unittest/unittest.h"
|
|
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <tuple>
|
|
|
|
|
|
namespace {
|
|
|
|
template <typename Test>
|
|
void runSyntaxTest(Test&& t) {
|
|
if (false)
|
|
t();
|
|
}
|
|
|
|
// 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 std::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 std::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 std::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 =
|
|
std::function<std::unique_ptr<FunctorClonable>(const FunctorClonable&)>;
|
|
|
|
static CloningFunctionType getCloningFunction() {
|
|
return [](const FunctorClonable& c) {
|
|
return std::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 = std::function<std::unique_ptr<FunctorWithDynamicStateClonable>(
|
|
const FunctorWithDynamicStateClonable&)>;
|
|
|
|
static CloningFunctionType getCloningFunction() {
|
|
return [calls = 0](const FunctorWithDynamicStateClonable& c) mutable {
|
|
return std::make_unique<FunctorWithDynamicStateClonable>(c.data +
|
|
std::to_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 std::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 = std::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 = std::make_unique<AltClonableTest>();
|
|
|
|
mongo::clonable_ptr<AltClonableTest> p2 = p;
|
|
|
|
ASSERT_TRUE(p != p2);
|
|
}
|
|
|
|
{
|
|
mongo::clonable_ptr<Alt2ClonableTest> p;
|
|
|
|
p = std::make_unique<Alt2ClonableTest>();
|
|
|
|
mongo::clonable_ptr<Alt2ClonableTest> p2 = p;
|
|
|
|
ASSERT_TRUE(p != p2);
|
|
}
|
|
|
|
{
|
|
mongo::clonable_ptr<FunctorClonable, FunctorClonable::CloningFunctionType> p{
|
|
FunctorClonable::getCloningFunction()};
|
|
|
|
p = std::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 = std::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.
|
|
{
|
|
std::ignore = 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>{std::make_unique<Clonable>()};
|
|
}
|
|
|
|
// Test unique pointer construction (conversion)
|
|
{
|
|
auto acceptor = [](const mongo::clonable_ptr<Clonable>&) {
|
|
};
|
|
acceptor(std::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.
|
|
{
|
|
std::ignore = 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>{std::make_unique<Clonable>(),
|
|
Clonable::getCloningFunction()};
|
|
}
|
|
|
|
// Test augmented unique pointer construction
|
|
{
|
|
mongo::clonable_ptr<Clonable, CloneFactory>{std::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) {
|
|
runSyntaxTest(SyntaxTests::construction<ClonableTest>);
|
|
runSyntaxTest(SyntaxTests::construction<AltClonableTest>);
|
|
runSyntaxTest(SyntaxTests::construction<Alt2ClonableTest>);
|
|
runSyntaxTest(SyntaxTests::construction<RawPointerClonable>);
|
|
runSyntaxTest(SyntaxTests::construction<UniquePtrClonable>);
|
|
}
|
|
|
|
TEST(ClonablePtrSyntaxTests, augmentedConstruction) {
|
|
runSyntaxTest(
|
|
SyntaxTests::augmentedConstruction<FunctorClonable, FunctorClonable::CloningFunctionType>);
|
|
runSyntaxTest(
|
|
SyntaxTests::augmentedConstruction<FunctorWithDynamicStateClonable,
|
|
FunctorWithDynamicStateClonable::CloningFunctionType>);
|
|
}
|
|
|
|
TEST(ClonablePtrSyntaxTests, pointerOperations) {
|
|
runSyntaxTest(SyntaxTests::pointerOperations<ClonableTest>);
|
|
runSyntaxTest(SyntaxTests::pointerOperations<AltClonableTest>);
|
|
runSyntaxTest(SyntaxTests::pointerOperations<Alt2ClonableTest>);
|
|
runSyntaxTest(SyntaxTests::pointerOperations<RawPointerClonable>);
|
|
runSyntaxTest(SyntaxTests::pointerOperations<UniquePtrClonable>);
|
|
}
|
|
|
|
TEST(ClonablePtrSyntaxTests, equalityOperations) {
|
|
runSyntaxTest(SyntaxTests::equalityOperations<ClonableTest>);
|
|
runSyntaxTest(SyntaxTests::equalityOperations<AltClonableTest>);
|
|
runSyntaxTest(SyntaxTests::equalityOperations<Alt2ClonableTest>);
|
|
runSyntaxTest(SyntaxTests::equalityOperations<RawPointerClonable>);
|
|
runSyntaxTest(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 std::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{std::make_unique<DetectDestruction>()};
|
|
ASSERT_EQ(DetectDestruction::activeCount, 1);
|
|
}
|
|
{
|
|
DestructionGuard check;
|
|
mongo::clonable_ptr<DetectDestruction> p = std::make_unique<DetectDestruction>();
|
|
ASSERT_EQ(DetectDestruction::activeCount, 1);
|
|
}
|
|
|
|
// Two separate constructions are unlinked
|
|
{
|
|
DestructionGuard check;
|
|
|
|
mongo::clonable_ptr<DetectDestruction> p1{std::make_unique<DetectDestruction>()};
|
|
ASSERT_EQ(DetectDestruction::activeCount, 1);
|
|
|
|
{
|
|
mongo::clonable_ptr<DetectDestruction> p2{std::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 = std::make_unique<mongo::clonable_ptr<DetectDestruction>>(
|
|
std::make_unique<DetectDestruction>());
|
|
ASSERT_EQ(DetectDestruction::activeCount, 1);
|
|
|
|
auto p2 = std::make_unique<mongo::clonable_ptr<DetectDestruction>>(
|
|
std::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 = std::make_unique<DetectDestruction>();
|
|
mongo::clonable_ptr<DetectDestruction> b = std::make_unique<DetectDestruction>();
|
|
mongo::clonable_ptr<DetectDestruction> c = std::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 = std::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 = std::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 std::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 = std::make_unique<ClonableObject>(1);
|
|
mongo::clonable_ptr<ClonableObject> q = std::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 override {
|
|
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 + std::to_string(++generation);
|
|
}
|
|
};
|
|
|
|
class StorageImplementation : public Interface {
|
|
private:
|
|
std::string store;
|
|
|
|
StorageImplementation* clone_impl() const override {
|
|
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 = std::make_unique<StorageImplementation>();
|
|
|
|
sink = instance;
|
|
|
|
ASSERT(instance.get() != sink.get());
|
|
|
|
instance = std::make_unique<GeneratorImplementation>("base message");
|
|
|
|
|
|
source = std::move(instance);
|
|
|
|
|
|
sink->consumeText(source->produceText());
|
|
}
|
|
|
|
} // namespace BehaviorTests
|
|
} // namespace
|