blob: c589a2e586dc3090a3f1cde6deeb9a83a0f27ebd [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "util/ref_counted.h"
#include <atomic>
#include <thread>
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/synchronization/notification.h"
namespace ipcz {
namespace {
using RefCountedTest = testing::Test;
class TestObject : public RefCounted {
public:
explicit TestObject(bool& destruction_flag)
: destruction_flag_(destruction_flag) {}
size_t count() const { return count_.load(std::memory_order_acquire); }
void Increment() { count_.fetch_add(1, std::memory_order_relaxed); }
private:
~TestObject() override { destruction_flag_ = true; }
bool& destruction_flag_;
std::atomic<size_t> count_{0};
};
TEST_F(RefCountedTest, NullRef) {
Ref<TestObject> ref;
EXPECT_FALSE(ref);
ref.reset();
EXPECT_FALSE(ref);
Ref<TestObject> other1 = ref;
EXPECT_FALSE(ref);
EXPECT_FALSE(other1);
Ref<TestObject> other2 = std::move(ref);
EXPECT_FALSE(ref);
EXPECT_FALSE(other2);
ref = other1;
EXPECT_FALSE(ref);
EXPECT_FALSE(other1);
ref = std::move(other2);
EXPECT_FALSE(ref);
EXPECT_FALSE(other2);
}
TEST_F(RefCountedTest, SimpleRef) {
bool destroyed = false;
auto ref = MakeRefCounted<TestObject>(destroyed);
EXPECT_TRUE(ref);
EXPECT_FALSE(destroyed);
ref.reset();
EXPECT_FALSE(ref);
EXPECT_TRUE(destroyed);
}
TEST_F(RefCountedTest, Copy) {
bool destroyed1 = false;
auto ref1 = MakeRefCounted<TestObject>(destroyed1);
Ref<TestObject> other1 = ref1;
EXPECT_TRUE(other1);
EXPECT_FALSE(destroyed1);
ref1.reset();
EXPECT_FALSE(ref1);
EXPECT_FALSE(destroyed1);
other1.reset();
EXPECT_FALSE(other1);
EXPECT_TRUE(destroyed1);
destroyed1 = false;
bool destroyed2 = false;
ref1 = MakeRefCounted<TestObject>(destroyed1);
auto ref2 = MakeRefCounted<TestObject>(destroyed2);
EXPECT_FALSE(destroyed1);
EXPECT_FALSE(destroyed2);
ref2 = ref1;
EXPECT_TRUE(ref1);
EXPECT_TRUE(ref2);
EXPECT_EQ(ref1, ref2);
EXPECT_FALSE(destroyed1);
EXPECT_TRUE(destroyed2);
ref1.reset();
EXPECT_FALSE(ref1);
EXPECT_FALSE(destroyed1);
EXPECT_TRUE(destroyed2);
ref2.reset();
EXPECT_FALSE(ref2);
EXPECT_TRUE(destroyed1);
}
TEST_F(RefCountedTest, Move) {
bool destroyed1 = false;
auto ref1 = MakeRefCounted<TestObject>(destroyed1);
Ref<TestObject> other1 = std::move(ref1);
EXPECT_TRUE(other1);
EXPECT_FALSE(ref1);
EXPECT_FALSE(destroyed1);
other1.reset();
EXPECT_TRUE(destroyed1);
destroyed1 = false;
bool destroyed2 = false;
ref1 = MakeRefCounted<TestObject>(destroyed1);
auto ref2 = MakeRefCounted<TestObject>(destroyed2);
EXPECT_FALSE(destroyed1);
EXPECT_FALSE(destroyed2);
ref2 = std::move(ref1);
EXPECT_FALSE(ref1);
EXPECT_TRUE(ref2);
EXPECT_FALSE(destroyed1);
EXPECT_TRUE(destroyed2);
ref2.reset();
EXPECT_TRUE(destroyed1);
}
TEST_F(RefCountedTest, ThreadSafe) {
bool destroyed = false;
auto counter = MakeRefCounted<TestObject>(destroyed);
constexpr size_t kIncrementsPerThread = 10000;
constexpr size_t kNumThreads = 64;
auto incrementer = [](Ref<TestObject> ref) {
for (size_t i = 0; i < kIncrementsPerThread; ++i) {
Ref<TestObject> copy = ref;
copy->Increment();
}
};
std::vector<std::thread> threads;
for (size_t i = 0; i < kNumThreads; ++i) {
threads.emplace_back(incrementer, counter);
}
for (std::thread& thread : threads) {
thread.join();
}
EXPECT_FALSE(destroyed);
EXPECT_EQ(kNumThreads * kIncrementsPerThread, counter->count());
counter.reset();
EXPECT_TRUE(destroyed);
}
} // namespace
} // namespace ipcz