vtk-m/vtkm/cont/testing/UnitTestToken.cxx
2021-04-12 18:51:53 -04:00

185 lines
5.2 KiB
C++

//============================================================================
// Copyright (c) Kitware, Inc.
// All rights reserved.
// See LICENSE.txt for details.
//
// This software is distributed WITHOUT ANY WARRANTY; without even
// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the above copyright notice for more information.
//============================================================================
#include <vtkm/cont/Token.h>
#include <vtkm/cont/testing/Testing.h>
#include <chrono>
#include <future>
#include <thread>
namespace
{
struct TestObject
{
vtkm::cont::Token::ReferenceCount* TokenCount;
std::mutex* Mutex;
std::condition_variable* ConditionVariable;
vtkm::Id* ReferenceCount;
TestObject()
: TokenCount(new vtkm::cont::Token::ReferenceCount(0))
, Mutex(new std::mutex)
, ConditionVariable(new std::condition_variable)
, ReferenceCount(new vtkm::Id(1))
{
}
TestObject(const TestObject& src)
: TokenCount(src.TokenCount)
, Mutex(src.Mutex)
, ConditionVariable(src.ConditionVariable)
, ReferenceCount(src.ReferenceCount)
{
std::unique_lock<std::mutex> lock(*this->Mutex);
(*this->ReferenceCount)++;
}
~TestObject()
{
std::unique_lock<std::mutex> lock(*this->Mutex);
(*this->ReferenceCount)--;
if (*this->ReferenceCount == 0)
{
lock.unlock();
delete this->TokenCount;
delete this->Mutex;
delete this->ConditionVariable;
delete this->ReferenceCount;
}
}
void Attach(vtkm::cont::Token& token)
{
token.Attach(*this, this->TokenCount, this->Mutex, this->ConditionVariable);
}
};
#define CHECK_OBJECT(object, expectedTokens, expectedRefs) \
VTKM_TEST_ASSERT(*(object).TokenCount == (expectedTokens), \
"Expected object to have token count of ", \
(expectedTokens), \
". It actually was ", \
*(object).TokenCount); \
VTKM_TEST_ASSERT(*(object).ReferenceCount == (expectedRefs), \
"Expected object to have reference count of ", \
(expectedRefs), \
". It actually was ", \
*(object).ReferenceCount)
void TestBasicAttachDetatch()
{
std::cout << "Test basic attach detach." << std::endl;
std::cout << " Create objects" << std::endl;
TestObject object1;
TestObject object2;
TestObject object3;
CHECK_OBJECT(object1, 0, 1);
CHECK_OBJECT(object2, 0, 1);
CHECK_OBJECT(object3, 0, 1);
std::cout << " Create outer token" << std::endl;
vtkm::cont::Token outerToken;
std::cout << " Attach outer token" << std::endl;
object1.Attach(outerToken);
object2.Attach(outerToken);
object3.Attach(outerToken);
CHECK_OBJECT(object1, 1, 2);
CHECK_OBJECT(object2, 1, 2);
CHECK_OBJECT(object3, 1, 2);
{
std::cout << " Create/Attach inner token" << std::endl;
vtkm::cont::Token innerToken;
object1.Attach(innerToken);
object2.Attach(innerToken);
object3.Attach(innerToken);
CHECK_OBJECT(object1, 2, 3);
CHECK_OBJECT(object2, 2, 3);
CHECK_OBJECT(object3, 2, 3);
std::cout << " Recursively attach outer token" << std::endl;
object1.Attach(outerToken);
CHECK_OBJECT(object1, 2, 3);
CHECK_OBJECT(object2, 2, 3);
CHECK_OBJECT(object3, 2, 3);
std::cout << " Detach from inner token (through scoping)" << std::endl;
}
CHECK_OBJECT(object1, 1, 2);
CHECK_OBJECT(object2, 1, 2);
CHECK_OBJECT(object3, 1, 2);
std::cout << " Detatch outer token" << std::endl;
outerToken.DetachFromAll();
CHECK_OBJECT(object1, 0, 1);
CHECK_OBJECT(object2, 0, 1);
CHECK_OBJECT(object3, 0, 1);
}
void WaitForDetachment(TestObject* object)
{
std::unique_lock<std::mutex> lock(*object->Mutex);
object->ConditionVariable->wait(lock, [object] { return *object->TokenCount < 1; });
std::cout << " Thread woke up" << std::endl;
}
void TestThreadWake()
{
std::cout << "Testing thread wakeup" << std::endl;
TestObject object;
CHECK_OBJECT(object, 0, 1);
vtkm::cont::Token token;
object.Attach(token);
CHECK_OBJECT(object, 1, 2);
std::cout << " Launching coordinated thread" << std::endl;
auto future = std::async(std::launch::async, WaitForDetachment, &object);
std::cout << " Sleep 500 milliseconds for thread to lock" << std::endl;
// 500 milliseconds should be ample time for the spawned thread to launch. If the systems busy then
// we might actually unlock the object before the thread gets there, but hopefully on some
// systems it will test correctly.
std::this_thread::sleep_for(std::chrono::milliseconds(500));
std::cout << " Main thread woke up. Detach token." << std::endl;
token.DetachFromAll();
std::cout << " Wait for thread to finish. Could deadlock if did not properly wake." << std::endl;
future.get();
std::cout << " Returned to main thread" << std::endl;
CHECK_OBJECT(object, 0, 1);
}
void DoTest()
{
TestBasicAttachDetatch();
TestThreadWake();
}
} // anonymous namespace
int UnitTestToken(int argc, char* argv[])
{
return vtkm::cont::testing::Testing::Run(DoTest, argc, argv);
}