Skip to content

Commit

Permalink
iox-eclipse-iceoryx#1640 Make StaticLifetimeGuard tests independent
Browse files Browse the repository at this point in the history
Signed-off-by: Matthias Killat <[email protected]>
  • Loading branch information
MatthiasKillat committed Jan 24, 2023
1 parent 0abe2a6 commit f5c444b
Showing 1 changed file with 169 additions and 86 deletions.
255 changes: 169 additions & 86 deletions iceoryx_hoofs/test/moduletests/test_static_lifetime_guard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ namespace
{
using namespace ::testing;

// allows to create different types for independent tests
template <uint64_t N>
struct Foo
{
Foo()
Expand All @@ -49,26 +51,59 @@ struct Foo
uint32_t id;
};

uint32_t Foo::ctorCalled{0};
uint32_t Foo::dtorCalled{0};
uint32_t Foo::instancesCreated{0};
constexpr uint32_t FIRST_INSTANCE_ID{1};
constexpr uint32_t SECOND_INSTANCE_ID{2};

// to access StaticLifetimeGuard count for testing only
// note that this is fine, we access the right function and static variable defined in the base class
template <typename T>
class StaticLifetimeGuardAccessor : public iox::design_pattern::StaticLifetimeGuard<T>
template <uint64_t N>
uint32_t Foo<N>::ctorCalled{0};

template <uint64_t N>
uint32_t Foo<N>::dtorCalled{0};

template <uint64_t N>
uint32_t Foo<N>::instancesCreated{0};

template <uint64_t N>
using TestGuard = iox::design_pattern::StaticLifetimeGuard<Foo<N>>;

// create a bundle of types and functions that are relevant for the tests,
// since we need a different static type for each test
// to ensure independence
template <uint64_t N>
struct TestTypes : public TestGuard<N>
{
public:
using iox::design_pattern::StaticLifetimeGuard<T>::setCount;
};
// NB: using the base class methods would admit to argue that we test another type,
// hence we use this alias of the Guard
using Guard = TestGuard<N>;
using Foo = Foo<N>;

using TestGuard<N>::setCount;

// the first call to testInstance() creates a static instance
// that is guarded once implicitly

static Foo& instance()
{
static Foo& f = Guard::instance();
return f;
}

using Guard = iox::design_pattern::StaticLifetimeGuard<Foo>;
using CountAccessor = StaticLifetimeGuardAccessor<Foo>;
// init the instance but also reset the Foo ctor count,
// used at start of some tests to simplify counting
static Foo& initInstance()
{
static Foo& f = Guard::instance();
Foo::reset();
return f;
}
};

// the first call to instance() creates a static instance,
// g_instance is guarded once implicitly
Foo& g_instance = Guard::instance();
// each test must use a different N, __LINE__ is unique and portable,
// __COUNTER__ is not portable
// shorten the initialization via macro, needed due to __LINE__
#define INIT_TEST using T = TestTypes<__LINE__>

// each test uses its own type for maximum separation, so we d not need to reset anything
class StaticLifetimeGuard_test : public Test
{
public:
Expand All @@ -78,158 +113,206 @@ class StaticLifetimeGuard_test : public Test

void TearDown() override
{
Foo::reset();
}
};

// we rely on test order here
TEST_F(StaticLifetimeGuard_test, countIsZeroIfNoInstanceExists)
{
::testing::Test::RecordProperty("TEST_ID", "0bf772c8-97c7-4cdb-80a1-e1b6a1a4fdc6");
INIT_TEST;

EXPECT_EQ(T::Guard::count(), 0);
EXPECT_EQ(T::Foo::ctorCalled, 0);
EXPECT_EQ(T::Foo::dtorCalled, 0);
}

TEST_F(StaticLifetimeGuard_test, guardDoesNotImplyInstanceConstructionIfInstanceIsNotCreated)
{
::testing::Test::RecordProperty("TEST_ID", "0db1455e-1b1f-4498-af3c-5e2d7e92180b");
INIT_TEST;

{
T::Guard g;
EXPECT_EQ(T::Guard::count(), 1);
}

EXPECT_EQ(T::Guard::count(), 0);
EXPECT_EQ(T::Foo::ctorCalled, 0);
EXPECT_EQ(T::Foo::dtorCalled, 0);
}

TEST_F(StaticLifetimeGuard_test, staticInitializationSucceeded)
{
::testing::Test::RecordProperty("TEST_ID", "d38b436b-f079-43fe-9d33-23d18cd08ffc");
INIT_TEST;

// g_instance was constructed and the instance still exists
EXPECT_EQ(g_instance.id, 1);
EXPECT_EQ(Guard::count(), 1);
EXPECT_EQ(Foo::ctorCalled, 1);
EXPECT_EQ(Foo::dtorCalled, 0);
// testInstance() was constructed and the instance still exists
EXPECT_EQ(T::instance().id, FIRST_INSTANCE_ID);
EXPECT_EQ(T::Guard::count(), 1);
EXPECT_EQ(T::Foo::ctorCalled, 1);
EXPECT_EQ(T::Foo::dtorCalled, 0);
}

// setCount is not part of the public interface but still useful to check whether it works
TEST_F(StaticLifetimeGuard_test, setCountWorks)
{
::testing::Test::RecordProperty("TEST_ID", "1db790f9-d49e-44b2-b7e9-af50dd6a7d67");
INIT_TEST;

auto oldCount = CountAccessor::setCount(73);
EXPECT_EQ(Guard::count(), 73);
T::Guard guard;
auto oldCount = T::setCount(73);
EXPECT_EQ(T::Guard::count(), 73);
EXPECT_EQ(oldCount, 1);

CountAccessor::setCount(oldCount);
EXPECT_EQ(Guard::count(), oldCount);
}

TEST_F(StaticLifetimeGuard_test, guardPreventsDestruction)
{
::testing::Test::RecordProperty("TEST_ID", "5a8c5953-f2d7-4539-89ba-b4686bbb6319");
EXPECT_EQ(Foo::ctorCalled, 0);
EXPECT_EQ(Foo::dtorCalled, 0);
EXPECT_EQ(g_instance.id, 1);
INIT_TEST;
T::initInstance();

EXPECT_EQ(T::instance().id, FIRST_INSTANCE_ID);
{
Guard guard;
EXPECT_EQ(Guard::count(), 2);
auto& instance = Guard::instance();
T::Guard guard;
EXPECT_EQ(T::count(), 2);
auto& instance = T::Guard::instance();

EXPECT_EQ(Foo::ctorCalled, 0);
EXPECT_EQ(Foo::dtorCalled, 0);
EXPECT_EQ(T::Foo::ctorCalled, 0);
EXPECT_EQ(T::Foo::dtorCalled, 0);

// still the same instance as g_instance
// still the same instance as testInstance()
EXPECT_EQ(instance.id, 1);
EXPECT_EQ(&instance, &g_instance);
EXPECT_EQ(&instance, &T::instance());
}

// the implicit guard of g_instance prevents destruction
EXPECT_EQ(Foo::ctorCalled, 0);
EXPECT_EQ(Foo::dtorCalled, 0);
EXPECT_EQ(g_instance.id, 1);
// the implicit guard of testInstance() prevents destruction
EXPECT_EQ(T::Foo::ctorCalled, 0);
EXPECT_EQ(T::Foo::dtorCalled, 0);
EXPECT_EQ(T::instance().id, FIRST_INSTANCE_ID);
}

TEST_F(StaticLifetimeGuard_test, copyIncreasesLifetimeCount)
{
::testing::Test::RecordProperty("TEST_ID", "6ab6396d-7c63-4626-92ed-c7f3ea67bbf1");
Guard guard;
INIT_TEST;
T::initInstance();

EXPECT_EQ(T::instance().id, FIRST_INSTANCE_ID);

T::Guard guard;
{
EXPECT_EQ(Guard::count(), 2);
EXPECT_EQ(T::Guard::count(), 2);
// NOLINTJUSTIFICATION ctor and dtor side effects are tested
// NOLINTNEXTLINE(performance-unnecessary-copy-initialization)
Guard copy(guard);
EXPECT_EQ(Guard::count(), 3);
T::Guard copy(guard);
EXPECT_EQ(T::Guard::count(), 3);
}
EXPECT_EQ(Guard::count(), 2);
EXPECT_EQ(T::Guard::count(), 2);

EXPECT_EQ(Foo::ctorCalled, 0);
EXPECT_EQ(Foo::dtorCalled, 0);
EXPECT_EQ(T::Foo::ctorCalled, 0);
EXPECT_EQ(T::Foo::dtorCalled, 0);
}

TEST_F(StaticLifetimeGuard_test, moveIncreasesLifetimeCount)
{
::testing::Test::RecordProperty("TEST_ID", "32a2fdbf-cb02-408c-99a3-373aa66b2764");
Guard guard;
INIT_TEST;
T::initInstance();

T::Guard guard;
{
EXPECT_EQ(Guard::count(), 2);
EXPECT_EQ(T::Guard::count(), 2);
// NOLINTJUSTIFICATION ctor and dtor side effects are tested
// NOLINTNEXTLINE(performance-unnecessary-copy-initialization)
Guard movedGuard(std::move(guard));
EXPECT_EQ(Guard::count(), 3);
T::Guard movedGuard(std::move(guard));
EXPECT_EQ(T::Guard::count(), 3);
}
EXPECT_EQ(Guard::count(), 2);
EXPECT_EQ(T::Guard::count(), 2);

EXPECT_EQ(Foo::ctorCalled, 0);
EXPECT_EQ(Foo::dtorCalled, 0);
EXPECT_EQ(T::Foo::ctorCalled, 0);
EXPECT_EQ(T::Foo::dtorCalled, 0);
}

TEST_F(StaticLifetimeGuard_test, assignmentDoesNotChangeLifetimeCount)
{
::testing::Test::RecordProperty("TEST_ID", "1c04ac75-d47a-44da-b8dc-6f567a53d3fc");
Guard guard1;
Guard guard2;
INIT_TEST;
T::initInstance();

T::Guard guard1;
T::Guard guard2;

EXPECT_EQ(Guard::count(), 3);
EXPECT_EQ(T::Guard::count(), 3);
guard1 = guard2;
EXPECT_EQ(Guard::count(), 3);
EXPECT_EQ(T::Guard::count(), 3);
guard1 = std::move(guard2);
EXPECT_EQ(Guard::count(), 3);
EXPECT_EQ(T::Guard::count(), 3);

EXPECT_EQ(Foo::ctorCalled, 0);
EXPECT_EQ(Foo::dtorCalled, 0);
EXPECT_EQ(T::Foo::ctorCalled, 0);
EXPECT_EQ(T::Foo::dtorCalled, 0);
}

TEST_F(StaticLifetimeGuard_test, destructionAtZeroCountWorks)
{
::testing::Test::RecordProperty("TEST_ID", "8b5a22a9-87bc-434b-9d07-9f3c20a6944e");
INIT_TEST;
T::initInstance();

{
Guard guard;
auto& instance = Guard::instance();
T::Guard guard;
auto& instance = T::Guard::instance();

// count is expected to be 2,
// we ignore the guard of g_instance by setting it to 1,
// we ignore the guard of testInstance() by setting it to 1,
// hence when guard is destroyed the instance will be destroyed as well
auto oldCount = CountAccessor::setCount(1);
auto oldCount = T::setCount(1);
EXPECT_EQ(oldCount, 2);

EXPECT_EQ(Foo::ctorCalled, 0);
EXPECT_EQ(Foo::dtorCalled, 0);
EXPECT_EQ(instance.id, 1);
EXPECT_EQ(T::Foo::ctorCalled, 0);
EXPECT_EQ(T::Foo::dtorCalled, 0);
EXPECT_EQ(instance.id, FIRST_INSTANCE_ID);
}

EXPECT_EQ(Guard::count(), 0);
EXPECT_EQ(Foo::ctorCalled, 0);
EXPECT_EQ(Foo::dtorCalled, 1);
EXPECT_EQ(T::Guard::count(), 0);
EXPECT_EQ(T::Foo::ctorCalled, 0);
EXPECT_EQ(T::Foo::dtorCalled, 1);
}

TEST_F(StaticLifetimeGuard_test, constructionAfterDestructionWorks)
{
::testing::Test::RecordProperty("TEST_ID", "0077e73d-ddf5-47e7-a7c6-93819f376175");
// ensure that the old instance is destroyed if it exists
if (Guard::count() > 0)
INIT_TEST;
T::initInstance();

{
Guard guard;
CountAccessor::setCount(1);
// now the instance will be destroyed once the guard is destroyed
T::Guard guard;
auto& instance = T::Guard::instance();

T::setCount(1);
EXPECT_EQ(instance.id, FIRST_INSTANCE_ID);
}

EXPECT_EQ(Guard::count(), 0);
// first instance destroyed (should usually only happen at the the program
// during static destruction)

T::Foo::reset();

EXPECT_EQ(T::Guard::count(), 0);
{
Guard guard;
auto& instance = Guard::instance();
T::Guard guard;
auto& instance = T::Guard::instance();

EXPECT_EQ(Foo::ctorCalled, 1);
EXPECT_EQ(Foo::dtorCalled, 0);
EXPECT_EQ(instance.id, 2);
EXPECT_EQ(T::Foo::ctorCalled, 1);
EXPECT_EQ(T::Foo::dtorCalled, 0);
EXPECT_EQ(instance.id, SECOND_INSTANCE_ID);
}

EXPECT_EQ(Guard::count(), 0);
EXPECT_EQ(Foo::ctorCalled, 1);
EXPECT_EQ(Foo::dtorCalled, 1);
// there was only one guard for the second instance that is destroyed
// at scope end and hence the second instance should be destroyed as well

EXPECT_EQ(T::Guard::count(), 0);
EXPECT_EQ(T::Foo::ctorCalled, 1);
EXPECT_EQ(T::Foo::dtorCalled, 1);
}

} // namespace

0 comments on commit f5c444b

Please sign in to comment.