1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
|
#include <catch2/matchers/catch_matchers_string.hpp>
#include <tango/tango.h>
#include <memory>
#include <omnithread.h>
#include "utils/utils.h"
// A thread that holds a TangoMonitor for the duration it is running.
class HoldMonitorThread : public omni_thread
{
public:
HoldMonitorThread(const HoldMonitorThread &) = delete;
HoldMonitorThread(HoldMonitorThread &&) = delete;
HoldMonitorThread &operator=(const HoldMonitorThread &) = delete;
HoldMonitorThread &operator=(HoldMonitorThread &&) = delete;
private:
static void stop_and_join(HoldMonitorThread *thread)
{
thread->m_done.post();
thread->join(nullptr);
}
public:
// `omni_thread` has a weird API where you are not supposed to call `delete`,
// but instead `join`, however, we need an RAII wrapper around this so that
// we clean up in the case a `REQUIRE_<X>(..)` fails (which will throw
// an exception). This unique_ptr will handle this for us.
using UniquePtr = std::unique_ptr<HoldMonitorThread, decltype(&stop_and_join)>;
static UniquePtr create(const char *name)
{
return {new HoldMonitorThread{name}, stop_and_join};
}
// Wait until the thread has grabbed the monitor.
void wait_until_started()
{
m_ready.wait();
}
// When called after `wait_until_started` this should throw a timeout error.
void grab_monitor()
{
m_monitor.get_monitor();
}
private:
HoldMonitorThread(const char *name) :
m_monitor{name},
m_ready{0},
m_done{0}
{
m_monitor.timeout(500); // 500 ms
start_undetached();
}
~HoldMonitorThread() override { }
void *run_undetached(void *) override
{
Tango::AutoTangoMonitor guard{&m_monitor};
m_ready.post();
m_done.wait();
return nullptr;
}
Tango::TangoMonitor m_monitor; // The monitor being held by the thread
omni_semaphore m_ready; // post()'d when by the thread when it has grabbed the lock
omni_semaphore m_done; // post()'d when by the test when it has finished
};
SCENARIO("TangoMonitor provides good error messages")
{
constexpr const char *k_name = "a-descriptive-name";
GIVEN("a named TangoMonitor lock by another thread")
{
auto thread = HoldMonitorThread::create(k_name);
WHEN("we try to grab the monitor")
{
thread->wait_until_started();
THEN("we timeout with a DevFailed that mentions the lock's name and the threads involved")
{
using Catch::Matchers::ContainsSubstring;
using TangoTest::AnyErrorMatches;
using TangoTest::DescriptionMatches;
using TangoTest::Reason;
std::string self = "Thread " + std::to_string(omni_thread::self()->id());
std::string other = "held by thread " + std::to_string(thread->id());
REQUIRE_THROWS_MATCHES(
thread->grab_monitor(),
Tango::DevFailed,
AnyErrorMatches(Reason(Tango::API_CommandTimedOut) &&
DescriptionMatches(ContainsSubstring(k_name) && ContainsSubstring(self) &&
ContainsSubstring(other))));
}
}
}
}
|