|
|
|
#include <chrono>
|
|
|
|
#include <iostream>
|
|
|
|
#include <thread>
|
|
|
|
#include <utility>
|
|
|
|
|
|
|
|
#include <cm3p/uv.h>
|
|
|
|
|
|
|
|
#include "cmUVHandlePtr.h"
|
|
|
|
|
|
|
|
static void signal_reset_fn(uv_async_t* handle)
|
|
|
|
{
|
|
|
|
auto ptr = static_cast<cm::uv_async_ptr*>(handle->data);
|
|
|
|
ptr->reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
// A common pattern is to use an async signal to shutdown the server.
|
|
|
|
static bool testAsyncShutdown()
|
|
|
|
{
|
|
|
|
uv_loop_t Loop;
|
|
|
|
auto err = uv_loop_init(&Loop);
|
|
|
|
if (err != 0) {
|
|
|
|
std::cerr << "Could not init loop" << std::endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
cm::uv_async_ptr signal;
|
|
|
|
signal.init(Loop, &signal_reset_fn, &signal);
|
|
|
|
|
|
|
|
std::thread([&] {
|
|
|
|
std::this_thread::sleep_for(std::chrono::seconds(2));
|
|
|
|
signal.send();
|
|
|
|
})
|
|
|
|
.detach();
|
|
|
|
|
|
|
|
if (uv_run(&Loop, UV_RUN_DEFAULT) != 0) {
|
|
|
|
std::cerr << "Unclean exit state in testAsyncDtor" << std::endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (signal.get()) {
|
|
|
|
std::cerr << "Loop exited with signal not being cleaned up" << std::endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
uv_loop_close(&Loop);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void signal_fn(uv_async_t*)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
// Async dtor is sort of a pain; since it locks a mutex we must be sure its
|
|
|
|
// dtor always calls reset otherwise the mutex is deleted then locked.
|
|
|
|
static bool testAsyncDtor()
|
|
|
|
{
|
|
|
|
uv_loop_t Loop;
|
|
|
|
auto err = uv_loop_init(&Loop);
|
|
|
|
if (err != 0) {
|
|
|
|
std::cerr << "Could not init loop" << std::endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
cm::uv_async_ptr signal;
|
|
|
|
signal.init(Loop, signal_fn);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (uv_run(&Loop, UV_RUN_DEFAULT) != 0) {
|
|
|
|
std::cerr << "Unclean exit state in testAsyncDtor" << std::endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
uv_loop_close(&Loop);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Async needs a relatively stateful deleter; make sure that is properly
|
|
|
|
// accounted for and doesn't try to hold on to invalid state when it is
|
|
|
|
// moved
|
|
|
|
static bool testAsyncMove()
|
|
|
|
{
|
|
|
|
uv_loop_t Loop;
|
|
|
|
auto err = uv_loop_init(&Loop);
|
|
|
|
if (err != 0) {
|
|
|
|
std::cerr << "Could not init loop" << std::endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
cm::uv_async_ptr signal;
|
|
|
|
{
|
|
|
|
cm::uv_async_ptr signalTmp;
|
|
|
|
signalTmp.init(Loop, signal_fn);
|
|
|
|
signal = std::move(signalTmp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (uv_run(&Loop, UV_RUN_DEFAULT) != 0) {
|
|
|
|
std::cerr << "Unclean exit state in testAsyncDtor" << std::endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
uv_loop_close(&Loop);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// When a type is castable to another uv type (pipe -> stream) here,
|
|
|
|
// and the deleter is convertible as well, we should allow moves from
|
|
|
|
// one type to the other.
|
|
|
|
static bool testCrossAssignment()
|
|
|
|
{
|
|
|
|
uv_loop_t Loop;
|
|
|
|
auto err = uv_loop_init(&Loop);
|
|
|
|
if (err != 0) {
|
|
|
|
std::cerr << "Could not init loop" << std::endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
cm::uv_pipe_ptr pipe;
|
|
|
|
pipe.init(Loop, 0);
|
|
|
|
|
|
|
|
cm::uv_stream_ptr stream = std::move(pipe);
|
|
|
|
if (pipe.get()) {
|
|
|
|
std::cerr << "Move should be sure to invalidate the previous ptr"
|
|
|
|
<< std::endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
cm::uv_handle_ptr handle = std::move(stream);
|
|
|
|
if (stream.get()) {
|
|
|
|
std::cerr << "Move should be sure to invalidate the previous ptr"
|
|
|
|
<< std::endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (uv_run(&Loop, UV_RUN_DEFAULT) != 0) {
|
|
|
|
std::cerr << "Unclean exit state in testCrossAssignment" << std::endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
uv_loop_close(&Loop);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// This test can't fail at run time; but this makes sure we have all our move
|
|
|
|
// ctors created correctly.
|
|
|
|
static bool testAllMoves()
|
|
|
|
{
|
|
|
|
using namespace cm;
|
|
|
|
struct allTypes
|
|
|
|
{
|
|
|
|
uv_stream_ptr _7;
|
|
|
|
uv_timer_ptr _8;
|
|
|
|
uv_tty_ptr _9;
|
|
|
|
uv_process_ptr _11;
|
|
|
|
uv_pipe_ptr _12;
|
|
|
|
uv_async_ptr _13;
|
|
|
|
uv_signal_ptr _14;
|
|
|
|
uv_handle_ptr _15;
|
|
|
|
};
|
|
|
|
|
|
|
|
allTypes a;
|
|
|
|
allTypes b(std::move(a));
|
|
|
|
allTypes c = std::move(b);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool testLoopReset()
|
|
|
|
{
|
|
|
|
bool closed = false;
|
|
|
|
cm::uv_loop_ptr loop;
|
|
|
|
loop.init();
|
|
|
|
|
|
|
|
uv_timer_t timer;
|
|
|
|
uv_timer_init(loop, &timer);
|
|
|
|
timer.data = &closed;
|
|
|
|
uv_close(reinterpret_cast<uv_handle_t*>(&timer), [](uv_handle_t* handle) {
|
|
|
|
auto closedPtr = static_cast<bool*>(handle->data);
|
|
|
|
*closedPtr = true;
|
|
|
|
});
|
|
|
|
|
|
|
|
loop.reset();
|
|
|
|
if (!closed) {
|
|
|
|
std::cerr << "uv_loop_ptr did not finish" << std::endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool testLoopDestructor()
|
|
|
|
{
|
|
|
|
bool closed = false;
|
|
|
|
|
|
|
|
uv_timer_t timer;
|
|
|
|
{
|
|
|
|
cm::uv_loop_ptr loop;
|
|
|
|
loop.init();
|
|
|
|
|
|
|
|
uv_timer_init(loop, &timer);
|
|
|
|
timer.data = &closed;
|
|
|
|
uv_close(reinterpret_cast<uv_handle_t*>(&timer), [](uv_handle_t* handle) {
|
|
|
|
auto closedPtr = static_cast<bool*>(handle->data);
|
|
|
|
*closedPtr = true;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!closed) {
|
|
|
|
std::cerr << "uv_loop_ptr did not finish" << std::endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
int testUVRAII(int, char** const)
|
|
|
|
{
|
|
|
|
if ((testAsyncShutdown() &&
|
|
|
|
testAsyncDtor() & testAsyncMove() & testCrossAssignment() &
|
|
|
|
testAllMoves() & testLoopReset() & testLoopDestructor()) == 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|