blob: 36b51503d48c96bc1a692acffa9f15346fedfeca [file]
/*
* Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
#include "SafePoint.hpp"
#include <atomic>
#include <optional>
#include <vector>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "concurrent/ScopedThread.hpp"
#include "TestSupport.hpp"
using namespace kotlin;
TEST(SafePointTest, SafePointActivator) {
ASSERT_FALSE(mm::test_support::safePointsAreActive());
{
mm::SafePointActivator activator;
ASSERT_TRUE(mm::test_support::safePointsAreActive());
}
ASSERT_FALSE(mm::test_support::safePointsAreActive());
}
TEST(SafePointTest, SafePointActivatorNested) {
ASSERT_FALSE(mm::test_support::safePointsAreActive());
{
mm::SafePointActivator activator;
ASSERT_TRUE(mm::test_support::safePointsAreActive());
{
mm::SafePointActivator activator;
ASSERT_TRUE(mm::test_support::safePointsAreActive());
}
ASSERT_TRUE(mm::test_support::safePointsAreActive());
}
ASSERT_FALSE(mm::test_support::safePointsAreActive());
}
TEST(SafePointTest, SafePointActivatorMove) {
ASSERT_FALSE(mm::test_support::safePointsAreActive());
{
std::optional<mm::SafePointActivator> activator;
ASSERT_FALSE(mm::test_support::safePointsAreActive());
{
mm::SafePointActivator innerActivator;
ASSERT_TRUE(mm::test_support::safePointsAreActive());
activator = std::move(innerActivator);
}
ASSERT_TRUE(mm::test_support::safePointsAreActive());
}
ASSERT_FALSE(mm::test_support::safePointsAreActive());
}
TEST(SafePointTest, StressSafePointActivator) {
std::atomic<size_t> initialized = 0;
std::atomic<bool> canStart = false;
std::atomic<size_t> started = 0;
std::atomic<bool> canStop = false;
std::atomic<size_t> stopped = 0;
std::vector<ScopedThread> threads;
for (int i = 0; i < kDefaultThreadCount; ++i) {
threads.emplace_back([&]() noexcept {
initialized.fetch_add(1, std::memory_order_relaxed);
while (!canStart.load(std::memory_order_relaxed)) {
std::this_thread::yield();
}
{
mm::SafePointActivator activator;
started.fetch_add(1, std::memory_order_relaxed);
while (!canStop.load(std::memory_order_relaxed)) {
std::this_thread::yield();
}
}
stopped.fetch_add(1, std::memory_order_relaxed);
});
}
while (initialized.load(std::memory_order_relaxed) < threads.size()) {
std::this_thread::yield();
}
EXPECT_FALSE(mm::test_support::safePointsAreActive());
canStart.store(true, std::memory_order_relaxed);
while (true) {
auto count = started.load(std::memory_order_relaxed);
if (count > 0) {
EXPECT_TRUE(mm::test_support::safePointsAreActive());
}
if (count == threads.size()) break;
std::this_thread::yield();
}
canStop.store(true, std::memory_order_relaxed);
while (stopped.load(std::memory_order_relaxed) < threads.size()) {
std::this_thread::yield();
}
EXPECT_FALSE(mm::test_support::safePointsAreActive());
}
class SafePointActionTest : public ::testing::Test {
public:
SafePointActionTest() noexcept {
EXPECT_THAT(instance_, nullptr);
instance_ = this;
}
~SafePointActionTest() {
EXPECT_THAT(instance_, this);
instance_ = nullptr;
}
testing::MockFunction<void(mm::ThreadData&)>& mockSafePoint() noexcept { return mockSafePoint_; }
static void action(mm::ThreadData& thread) noexcept { instance_->mockSafePoint_.Call(thread); }
private:
static SafePointActionTest* instance_;
testing::StrictMock<testing::MockFunction<void(mm::ThreadData&)>> mockSafePoint_;
};
// static
SafePointActionTest* SafePointActionTest::instance_ = nullptr;
TEST_F(SafePointActionTest, SafePoint) {
RunInNewThread([this](mm::ThreadData& thread) noexcept {
mm::test_support::setSafePointAction(&action);
EXPECT_CALL(mockSafePoint(), Call(testing::Ref(thread)));
mm::safePoint();
testing::Mock::VerifyAndClearExpectations(&mockSafePoint());
mm::test_support::setSafePointAction(nullptr);
});
}
TEST_F(SafePointActionTest, SafePointWithExplicitThread) {
RunInNewThread([this](mm::ThreadData& thread) noexcept {
mm::test_support::setSafePointAction(&action);
EXPECT_CALL(mockSafePoint(), Call(testing::Ref(thread)));
mm::safePoint(thread);
testing::Mock::VerifyAndClearExpectations(&mockSafePoint());
mm::test_support::setSafePointAction(nullptr);
});
}
class ExtraSafePointActionActivatorTest : public ::testing::Test {
public:
class ActivatorImpl : public mm::ExtraSafePointActionActivator<ActivatorImpl> {};
auto& mockAction() noexcept { return mockAction_; }
private:
testing::StrictMock<testing::MockFunction<void()>> mockAction_;
};
TEST_F(ExtraSafePointActionActivatorTest, ExtraActionActivator) {
ASSERT_FALSE(mm::test_support::safePointsAreActive());
ActivatorImpl::doIfActive([&] { mockAction().Call(); });
{
ActivatorImpl activator;
ASSERT_TRUE(mm::test_support::safePointsAreActive());
EXPECT_CALL(mockAction(), Call());
ActivatorImpl::doIfActive([&] { mockAction().Call(); });
testing::Mock::VerifyAndClearExpectations(&mockAction());
}
ActivatorImpl::doIfActive([&] { mockAction().Call(); });
ASSERT_FALSE(mm::test_support::safePointsAreActive());
}
TEST_F(ExtraSafePointActionActivatorTest, ExtraActionActivatorStress) {
std::atomic terminate = false;
std::vector<ScopedThread> threads;
for (int i = 0; i < kDefaultThreadCount; ++i) {
threads.emplace_back([&]() noexcept {
while (!terminate) {
ActivatorImpl::doIfActive([&] { mockAction().Call(); });
std::this_thread::yield();
}
});
}
EXPECT_CALL(mockAction(), Call()).Times(testing::AnyNumber());
{
ActivatorImpl activator;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
testing::Mock::VerifyAndClearExpectations(&mockAction());
terminate = true;
}