#include "ptr.h"
#include "vector.h"
#include "noncopyable.h"
#include <library/cpp/testing/unittest/registar.h>
#include <util/generic/hash_set.h>
#include <util/generic/is_in.h>
#include <util/stream/output.h>
#include <util/system/thread.h>
class TPointerTest: public TTestBase {
UNIT_TEST_SUITE(TPointerTest);
UNIT_TEST(TestTypedefs);
UNIT_TEST(TestSimpleIntrPtr);
UNIT_TEST(TestHolderPtr);
UNIT_TEST(TestHolderPtrMoveConstructor);
UNIT_TEST(TestHolderPtrMoveConstructorInheritance);
UNIT_TEST(TestHolderPtrMoveAssignment);
UNIT_TEST(TestHolderPtrMoveAssignmentInheritance);
UNIT_TEST(TestMakeHolder);
UNIT_TEST(TestTrulePtr);
UNIT_TEST(TestAutoToHolder);
UNIT_TEST(TestCopyPtr);
UNIT_TEST(TestIntrPtr);
UNIT_TEST(TestIntrusiveConvertion);
UNIT_TEST(TestIntrusiveConstConvertion);
UNIT_TEST(TestIntrusiveConstConstruction);
UNIT_TEST(TestMakeIntrusive);
UNIT_TEST(TestCopyOnWritePtr1);
UNIT_TEST(TestCopyOnWritePtr2);
UNIT_TEST(TestOperatorBool);
UNIT_TEST(TestMakeShared);
UNIT_TEST(TestComparison);
UNIT_TEST(TestSimpleIntrusivePtrCtorTsan);
UNIT_TEST(TestRefCountedPtrsInHashSet)
UNIT_TEST_SUITE_END();
private:
void TestSimpleIntrusivePtrCtorTsan() {
struct S: public TAtomicRefCount<S> {
};
struct TLocalThread: public ISimpleThread {
void* ThreadProc() override {
TSimpleIntrusivePtr<S> ptr;
return nullptr;
}
};
// Create TSimpleIntrusivePtr in different threads
// Its constructor should be thread-safe
TLocalThread t1, t2;
t1.Start();
t2.Start();
t1.Join();
t2.Join();
}
inline void TestTypedefs() {
TAtomicSharedPtr<int>(new int(1));
TSimpleSharedPtr<int>(new int(1));
}
void TestSimpleIntrPtr();
void TestHolderPtr();
void TestHolderPtrMoveConstructor();
void TestHolderPtrMoveConstructorInheritance();
void TestHolderPtrMoveAssignment();
void TestHolderPtrMoveAssignmentInheritance();
void TestMakeHolder();
void TestTrulePtr();
void TestAutoToHolder();
void TestCopyPtr();
void TestIntrPtr();
void TestIntrusiveConvertion();
void TestIntrusiveConstConvertion();
void TestIntrusiveConstConstruction();
void TestMakeIntrusive();
void TestCopyOnWritePtr1();
void TestCopyOnWritePtr2();
void TestOperatorBool();
void TestMakeShared();
void TestComparison();
template <class T, class TRefCountedPtr>
void TestRefCountedPtrsInHashSetImpl();
void TestRefCountedPtrsInHashSet();
};
UNIT_TEST_SUITE_REGISTRATION(TPointerTest);
static int cnt = 0;
class A: public TAtomicRefCount<A> {
public:
inline A() {
++cnt;
}
inline A(const A&)
: TAtomicRefCount<A>(*this)
{
++cnt;
}
inline ~A() {
--cnt;
}
};
static A* MakeA() {
return new A();
}
/*
* test compileability
*/
class B;
static TSimpleIntrusivePtr<B> GetB() {
throw 1;
}
void Func() {
TSimpleIntrusivePtr<B> b = GetB();
}
void TPointerTest::TestSimpleIntrPtr() {
{
TSimpleIntrusivePtr<A> a1(MakeA());
TSimpleIntrusivePtr<A> a2(MakeA());
TSimpleIntrusivePtr<A> a3 = a2;
a1 = a2;
a2 = a3;
}
UNIT_ASSERT_VALUES_EQUAL(cnt, 0);
}
void TPointerTest::TestHolderPtr() {
{
THolder<A> a1(MakeA());
THolder<A> a2(a1.Release());
}
UNIT_ASSERT_VALUES_EQUAL(cnt, 0);
}
THolder<int> CreateInt(int value) {
THolder<int> res(new int);
*res = value;
return res;
}
void TPointerTest::TestHolderPtrMoveConstructor() {
THolder<int> h = CreateInt(42);
UNIT_ASSERT_VALUES_EQUAL(*h, 42);
}
void TPointerTest::TestHolderPtrMoveAssignment() {
THolder<int> h(new int);
h = CreateInt(42);
UNIT_ASSERT_VALUES_EQUAL(*h, 42);
}
struct TBase {
virtual ~TBase() = default;
};
struct TDerived: public TBase {
};
void TPointerTest::TestHolderPtrMoveConstructorInheritance() {
// compileability test
THolder<TBase> basePtr(THolder<TDerived>(new TDerived));
}
void TPointerTest::TestHolderPtrMoveAssignmentInheritance() {
// compileability test
THolder<TBase> basePtr;
basePtr = THolder<TDerived>(new TDerived);
}
void TPointerTest::TestMakeHolder() {
{
auto ptr = MakeHolder<int>(5);
UNIT_ASSERT_VALUES_EQUAL(*ptr, 5);
}
{
struct TRec {
int X, Y;
TRec()
: X(1)
, Y(2)
{
}
};
THolder<TRec> ptr = MakeHolder<TRec>();
UNIT_ASSERT_VALUES_EQUAL(ptr->X, 1);
UNIT_ASSERT_VALUES_EQUAL(ptr->Y, 2);
}
{
struct TRec {
int X, Y;
TRec(int x, int y)
: X(x)
, Y(y)
{
}
};
auto ptr = MakeHolder<TRec>(1, 2);
UNIT_ASSERT_VALUES_EQUAL(ptr->X, 1);
UNIT_ASSERT_VALUES_EQUAL(ptr->Y, 2);
}
{
class TRec {
private:
int X_, Y_;
public:
TRec(int x, int y)
: X_(x)
, Y_(y)
{
}
int GetX() const {
return X_;
}
int GetY() const {
return Y_;
}
};
auto ptr = MakeHolder<TRec>(1, 2);
UNIT_ASSERT_VALUES_EQUAL(ptr->GetX(), 1);
UNIT_ASSERT_VALUES_EQUAL(ptr->GetY(), 2);
}
}
void TPointerTest::TestTrulePtr() {
{
TAutoPtr<A> a1(MakeA());
TAutoPtr<A> a2(a1);
a1 = a2;
}
UNIT_ASSERT_VALUES_EQUAL(cnt, 0);
}
void TPointerTest::TestAutoToHolder() {
{
TAutoPtr<A> a1(MakeA());
THolder<A> a2(a1);
UNIT_ASSERT_EQUAL(a1.Get(), nullptr);
UNIT_ASSERT_VALUES_EQUAL(cnt, 1);
}
UNIT_ASSERT_VALUES_EQUAL(cnt, 0);
{
TAutoPtr<A> x(new A());
THolder<const A> y = x;
}
UNIT_ASSERT_VALUES_EQUAL(cnt, 0);
{
class B1: public A {
};
TAutoPtr<B1> x(new B1());
THolder<A> y = x;
}
UNIT_ASSERT_VALUES_EQUAL(cnt, 0);
}
void TPointerTest::TestCopyPtr() {
TCopyPtr<A> a1(MakeA());
{
TCopyPtr<A> a2(MakeA());
TCopyPtr<A> a3 = a2;
UNIT_ASSERT_VALUES_EQUAL(cnt, 3);
a1 = a2;
a2 = a3;
}
UNIT_ASSERT_VALUES_EQUAL(cnt, 1);
a1.Destroy();
UNIT_ASSERT_VALUES_EQUAL(cnt, 0);
}
class TOp: public TSimpleRefCount<TOp>, public TNonCopyable {
public:
static int Cnt;
public:
TOp() {
++Cnt;
}
virtual ~TOp() {
--Cnt;
}
};
int TOp::Cnt = 0;
class TOp2: public TOp {
public:
TIntrusivePtr<TOp> Op;
public:
TOp2(const TIntrusivePtr<TOp>& op)
: Op(op)
{
++Cnt;
}
~TOp2() override {
--Cnt;
}
};
class TOp3 {
public:
TIntrusivePtr<TOp2> Op2;
};
void Attach(TOp3* op3, TIntrusivePtr<TOp>* op) {
TIntrusivePtr<TOp2> op2 = new TOp2(*op);
op3->Op2 = op2.Get();
*op = op2.Get();
}
void TPointerTest::TestIntrPtr() {
{
TIntrusivePtr<TOp> p, p2;
TOp3 op3;
{
TVector<TIntrusivePtr<TOp>> f1;
{
TVector<TIntrusivePtr<TOp>> f2;
f2.push_back(new TOp);
p = new TOp;
f2.push_back(p);
Attach(&op3, &f2[1]);
f1 = f2;
UNIT_ASSERT_VALUES_EQUAL(f1[0]->RefCount(), 2);
UNIT_ASSERT_VALUES_EQUAL(f1[1]->RefCount(), 3);
UNIT_ASSERT_EQUAL(f1[1].Get(), op3.Op2.Get());
UNIT_ASSERT_VALUES_EQUAL(op3.Op2->RefCount(), 3);
UNIT_ASSERT_VALUES_EQUAL(op3.Op2->Op->RefCount(), 2);
UNIT_ASSERT_VALUES_EQUAL(TOp::Cnt, 4);
}
p2 = p;
}
UNIT_ASSERT_VALUES_EQUAL(op3.Op2->RefCount(), 1);
UNIT_ASSERT_VALUES_EQUAL(op3.Op2->Op->RefCount(), 3);
UNIT_ASSERT_VALUES_EQUAL(TOp::Cnt, 3);
}
UNIT_ASSERT_VALUES_EQUAL(TOp::Cnt, 0);
}
namespace NTestIntrusiveConvertion {
struct TA: public TSimpleRefCount<TA> {
};
struct TAA: public TA {
};
struct TB: public TSimpleRefCount<TB> {
};
void Func(TIntrusivePtr<TA>) {
}
void Func(TIntrusivePtr<TB>) {
}
void Func(TIntrusiveConstPtr<TA>) {
}
void Func(TIntrusiveConstPtr<TB>) {
}
}
void TPointerTest::TestIntrusiveConvertion() {
using namespace NTestIntrusiveConvertion;
TIntrusivePtr<TAA> aa = new TAA;
UNIT_ASSERT_VALUES_EQUAL(aa->RefCount(), 1);
TIntrusivePtr<TA> a = aa;
UNIT_ASSERT_VALUES_EQUAL(aa->RefCount(), 2);
UNIT_ASSERT_VALUES_EQUAL(a->RefCount(), 2);
aa.Reset();
UNIT_ASSERT_VALUES_EQUAL(a->RefCount(), 1);
// test that Func(TIntrusivePtr<TB>) doesn't participate in overload resolution
Func(aa);
}
void TPointerTest::TestIntrusiveConstConvertion() {
using namespace NTestIntrusiveConvertion;
TIntrusiveConstPtr<TAA> aa = new TAA;
UNIT_ASSERT_VALUES_EQUAL(aa->RefCount(), 1);
TIntrusiveConstPtr<TA> a = aa;
UNIT_ASSERT_VALUES_EQUAL(aa->RefCount(), 2);
UNIT_ASSERT_VALUES_EQUAL(a->RefCount(), 2);
aa.Reset();
UNIT_ASSERT_VALUES_EQUAL(a->RefCount(), 1);
// test that Func(TIntrusiveConstPtr<TB>) doesn't participate in overload resolution
Func(aa);
}
void TPointerTest::TestMakeIntrusive() {
{
UNIT_ASSERT_VALUES_EQUAL(0, TOp::Cnt);
auto p = MakeIntrusive<TOp>();
UNIT_ASSERT_VALUES_EQUAL(1, p->RefCount());
UNIT_ASSERT_VALUES_EQUAL(1, TOp::Cnt);
}
UNIT_ASSERT_VALUES_EQUAL(TOp::Cnt, 0);
}
void TPointerTest::TestCopyOnWritePtr1() {
using TPtr = TCowPtr<TSimpleSharedPtr<int>>;
TPtr p1;
UNIT_ASSERT(!p1.Shared());
p1.Reset(new int(123));
UNIT_ASSERT(!p1.Shared());
{
TPtr pTmp = p1;
UNIT_ASSERT(p1.Shared());
UNIT_ASSERT(pTmp.Shared());
UNIT_ASSERT_EQUAL(p1.Get(), pTmp.Get());
}
UNIT_ASSERT(!p1.Shared());
TPtr p2 = p1;
TPtr p3;
p3 = p2;
UNIT_ASSERT(p2.Shared());
UNIT_ASSERT(p3.Shared());
UNIT_ASSERT_EQUAL(p1.Get(), p2.Get());
UNIT_ASSERT_EQUAL(p1.Get(), p3.Get());
*(p1.Mutable()) = 456;
UNIT_ASSERT(!p1.Shared());
UNIT_ASSERT(p2.Shared());
UNIT_ASSERT(p3.Shared());
UNIT_ASSERT_EQUAL(*p1, 456);
UNIT_ASSERT_EQUAL(*p2, 123);
UNIT_ASSERT_EQUAL(*p3, 123);
UNIT_ASSERT_UNEQUAL(p1.Get(), p2.Get());
UNIT_ASSERT_EQUAL(p2.Get(), p3.Get());
p2.Mutable();
UNIT_ASSERT(!p2.Shared());
UNIT_ASSERT(!p3.Shared());
UNIT_ASSERT_EQUAL(*p2, 123);
UNIT_ASSERT_EQUAL(*p3, 123);
UNIT_ASSERT_UNEQUAL(p2.Get(), p3.Get());
}
struct X: public TSimpleRefCount<X> {
inline X(int v = 0)
: V(v)
{
}
int V;
};
void TPointerTest::TestCopyOnWritePtr2() {
using TPtr = TCowPtr<TIntrusivePtr<X>>;
TPtr p1;
UNIT_ASSERT(!p1.Shared());
p1.Reset(new X(123));
UNIT_ASSERT(!p1.Shared());
{
TPtr pTmp = p1;
UNIT_ASSERT(p1.Shared());
UNIT_ASSERT(pTmp.Shared());
UNIT_ASSERT_EQUAL(p1.Get(), pTmp.Get());
}
UNIT_ASSERT(!p1.Shared());
TPtr p2 = p1;
TPtr p3;
p3 = p2;
UNIT_ASSERT(p2.Shared());
UNIT_ASSERT(p3.Shared());
UNIT_ASSERT_EQUAL(p1.Get(), p2.Get());
UNIT_ASSERT_EQUAL(p1.Get(), p3.Get());
p1.Mutable()->V = 456;
UNIT_ASSERT(!p1.Shared());
UNIT_ASSERT(p2.Shared());
UNIT_ASSERT(p3.Shared());
UNIT_ASSERT_EQUAL(p1->V, 456);
UNIT_ASSERT_EQUAL(p2->V, 123);
UNIT_ASSERT_EQUAL(p3->V, 123);
UNIT_ASSERT_UNEQUAL(p1.Get(), p2.Get());
UNIT_ASSERT_EQUAL(p2.Get(), p3.Get());
p2.Mutable();
UNIT_ASSERT(!p2.Shared());
UNIT_ASSERT(!p3.Shared());
UNIT_ASSERT_EQUAL(p2->V, 123);
UNIT_ASSERT_EQUAL(p3->V, 123);
UNIT_ASSERT_UNEQUAL(p2.Get(), p3.Get());
}
namespace {
template <class TFrom, class TTo>
struct TImplicitlyCastable {
struct RTYes {
char t[2];
};
using RTNo = char;
static RTYes Func(TTo);
static RTNo Func(...);
static TFrom Get();
/*
* Result == (TFrom could be converted to TTo implicitly)
*/
enum {
Result = (sizeof(Func(Get())) != sizeof(RTNo))
};
};
struct TImplicitlyCastableToBool {
inline operator bool() const {
return true;
}
};
}
void TPointerTest::TestOperatorBool() {
using TVec = TVector<ui32>;
// to be sure TImplicitlyCastable works as expected
UNIT_ASSERT((TImplicitlyCastable<int, bool>::Result));
UNIT_ASSERT((TImplicitlyCastable<double, int>::Result));
UNIT_ASSERT((TImplicitlyCastable<int*, void*>::Result));
UNIT_ASSERT(!(TImplicitlyCastable<void*, int*>::Result));
UNIT_ASSERT((TImplicitlyCastable<TImplicitlyCastableToBool, bool>::Result));
UNIT_ASSERT((TImplicitlyCastable<TImplicitlyCastableToBool, int>::Result));
UNIT_ASSERT((TImplicitlyCastable<TImplicitlyCastableToBool, ui64>::Result));
UNIT_ASSERT(!(TImplicitlyCastable<TImplicitlyCastableToBool, void*>::Result));
// pointers
UNIT_ASSERT(!(TImplicitlyCastable<TSimpleSharedPtr<TVec>, int>::Result));
UNIT_ASSERT(!(TImplicitlyCastable<TAutoPtr<ui64>, ui64>::Result));
UNIT_ASSERT(!(TImplicitlyCastable<THolder<TVec>, bool>::Result)); // even this
{
// mostly a compilability test
THolder<TVec> a;
UNIT_ASSERT(!a);
UNIT_ASSERT(!bool(a));
if (a) {
UNIT_ASSERT(false);
}
if (!a) {
UNIT_ASSERT(true);
}
a.Reset(new TVec);
UNIT_ASSERT(a);
UNIT_ASSERT(bool(a));
if (a) {
UNIT_ASSERT(true);
}
if (!a) {
UNIT_ASSERT(false);
}
THolder<TVec> b(new TVec);
UNIT_ASSERT(a.Get() != b.Get());
UNIT_ASSERT(a != b);
if (a == b) {
UNIT_ASSERT(false);
}
if (a != b) {
UNIT_ASSERT(true);
}
if (!(a && b)) {
UNIT_ASSERT(false);
}
if (a && b) {
UNIT_ASSERT(true);
}
// int i = a; // does not compile
// bool c = (a < b); // does not compile
}
}
void TPointerTest::TestMakeShared() {
{
TSimpleSharedPtr<int> ptr = MakeSimpleShared<int>(5);
UNIT_ASSERT_VALUES_EQUAL(*ptr, 5);
}
{
struct TRec {
int X, Y;
TRec()
: X(1)
, Y(2)
{
}
};
auto ptr = MakeAtomicShared<TRec>();
UNIT_ASSERT_VALUES_EQUAL(ptr->X, 1);
UNIT_ASSERT_VALUES_EQUAL(ptr->Y, 2);
}
{
struct TRec {
int X, Y;
};
TAtomicSharedPtr<TRec> ptr = MakeAtomicShared<TRec>(1, 2);
UNIT_ASSERT_VALUES_EQUAL(ptr->X, 1);
UNIT_ASSERT_VALUES_EQUAL(ptr->Y, 2);
}
{
class TRec {
private:
int X_, Y_;
public:
TRec(int x, int y)
: X_(x)
, Y_(y)
{
}
int GetX() const {
return X_;
}
int GetY() const {
return Y_;
}
};
TSimpleSharedPtr<TRec> ptr = MakeSimpleShared<TRec>(1, 2);
UNIT_ASSERT_VALUES_EQUAL(ptr->GetX(), 1);
UNIT_ASSERT_VALUES_EQUAL(ptr->GetY(), 2);
}
{
enum EObjectState {
OS_NOT_CREATED,
OS_CREATED,
OS_DESTROYED,
};
struct TObject {
EObjectState& State;
TObject(EObjectState& state)
: State(state)
{
State = OS_CREATED;
}
~TObject() {
State = OS_DESTROYED;
}
};
auto throwsException = []() {
throw yexception();
return 5;
};
auto testFunction = [](TSimpleSharedPtr<TObject>, int) {
};
EObjectState state = OS_NOT_CREATED;
try {
testFunction(MakeSimpleShared<TObject>(state), throwsException());
} catch (yexception&) {
}
UNIT_ASSERT(state == OS_NOT_CREATED || state == OS_DESTROYED);
}
}
template <class TPtr>
void TestPtrComparison(const TPtr& ptr) {
UNIT_ASSERT(ptr == ptr);
UNIT_ASSERT(!(ptr != ptr));
UNIT_ASSERT(ptr == ptr.Get());
UNIT_ASSERT(!(ptr != ptr.Get()));
}
void TPointerTest::TestComparison() {
THolder<A> ptr1(new A);
TAutoPtr<A> ptr2;
TSimpleSharedPtr<int> ptr3(new int(6));
TIntrusivePtr<A> ptr4;
TIntrusiveConstPtr<A> ptr5 = ptr4;
UNIT_ASSERT(ptr1 != nullptr);
UNIT_ASSERT(ptr2 == nullptr);
UNIT_ASSERT(ptr3 != nullptr);
UNIT_ASSERT(ptr4 == nullptr);
UNIT_ASSERT(ptr5 == nullptr);
TestPtrComparison(ptr1);
TestPtrComparison(ptr2);
TestPtrComparison(ptr3);
TestPtrComparison(ptr4);
TestPtrComparison(ptr5);
}
template <class T, class TRefCountedPtr>
void TPointerTest::TestRefCountedPtrsInHashSetImpl() {
THashSet<TRefCountedPtr> hashSet;
TRefCountedPtr p1(new T());
UNIT_ASSERT(!IsIn(hashSet, p1));
UNIT_ASSERT(hashSet.insert(p1).second);
UNIT_ASSERT(IsIn(hashSet, p1));
UNIT_ASSERT_VALUES_EQUAL(hashSet.size(), 1);
UNIT_ASSERT(!hashSet.insert(p1).second);
TRefCountedPtr p2(new T());
UNIT_ASSERT(!IsIn(hashSet, p2));
UNIT_ASSERT(hashSet.insert(p2).second);
UNIT_ASSERT(IsIn(hashSet, p2));
UNIT_ASSERT_VALUES_EQUAL(hashSet.size(), 2);
}
struct TCustomIntrusivePtrOps: TDefaultIntrusivePtrOps<A> {
};
struct TCustomDeleter: TDelete {
};
struct TCustomCounter: TSimpleCounter {
using TSimpleCounterTemplate::TSimpleCounterTemplate;
};
void TPointerTest::TestRefCountedPtrsInHashSet() {
// test common case
TestRefCountedPtrsInHashSetImpl<TString, TSimpleSharedPtr<TString>>();
TestRefCountedPtrsInHashSetImpl<TString, TAtomicSharedPtr<TString>>();
TestRefCountedPtrsInHashSetImpl<A, TIntrusivePtr<A>>();
TestRefCountedPtrsInHashSetImpl<A, TIntrusiveConstPtr<A>>();
// test with custom ops
TestRefCountedPtrsInHashSetImpl<TString, TSharedPtr<TString, TCustomCounter, TCustomDeleter>>();
TestRefCountedPtrsInHashSetImpl<A, TIntrusivePtr<A, TCustomIntrusivePtrOps>>();
TestRefCountedPtrsInHashSetImpl<A, TIntrusiveConstPtr<A, TCustomIntrusivePtrOps>>();
}
class TRefCountedWithStatistics: public TNonCopyable {
public:
struct TExternalCounter {
TAtomic Counter{0};
TAtomic Increments{0};
};
TRefCountedWithStatistics(TExternalCounter& cnt)
: ExternalCounter_(cnt)
{
ExternalCounter_ = {}; // reset counters
}
void Ref() noexcept {
AtomicIncrement(ExternalCounter_.Counter);
AtomicIncrement(ExternalCounter_.Increments);
}
void UnRef() noexcept {
if (AtomicDecrement(ExternalCounter_.Counter) == 0) {
TDelete::Destroy(this);
}
}
void DecRef() noexcept {
Y_VERIFY(AtomicDecrement(ExternalCounter_.Counter) != 0);
}
private:
TExternalCounter& ExternalCounter_;
};
void TPointerTest::TestIntrusiveConstConstruction() {
{
TRefCountedWithStatistics::TExternalCounter cnt;
UNIT_ASSERT_VALUES_EQUAL(AtomicGet(cnt.Counter), 0);
UNIT_ASSERT_VALUES_EQUAL(AtomicGet(cnt.Increments), 0);
TIntrusivePtr<TRefCountedWithStatistics> i{MakeIntrusive<TRefCountedWithStatistics>(cnt)};
UNIT_ASSERT_VALUES_EQUAL(AtomicGet(cnt.Counter), 1);
UNIT_ASSERT_VALUES_EQUAL(AtomicGet(cnt.Increments), 1);
i.Reset();
UNIT_ASSERT_VALUES_EQUAL(AtomicGet(cnt.Counter), 0);
UNIT_ASSERT_VALUES_EQUAL(AtomicGet(cnt.Increments), 1);
}
{
TRefCountedWithStatistics::TExternalCounter cnt;
UNIT_ASSERT_VALUES_EQUAL(AtomicGet(cnt.Counter), 0);
UNIT_ASSERT_VALUES_EQUAL(AtomicGet(cnt.Increments), 0);
TIntrusiveConstPtr<TRefCountedWithStatistics> c{MakeIntrusive<TRefCountedWithStatistics>(cnt)};
UNIT_ASSERT_VALUES_EQUAL(AtomicGet(cnt.Counter), 1);
UNIT_ASSERT_VALUES_EQUAL(AtomicGet(cnt.Increments), 1);
c.Reset();
UNIT_ASSERT_VALUES_EQUAL(AtomicGet(cnt.Counter), 0);
UNIT_ASSERT_VALUES_EQUAL(AtomicGet(cnt.Increments), 1);
}
}