mirror of
https://github.com/FairRootGroup/FairMQ.git
synced 2025-10-13 08:41:16 +00:00
Extend tests of error cases
- test raising SIGINT in every state - test going to Error state from every state - add new states (bind/connect) to exception tests
This commit is contained in:
parent
44a9946ea6
commit
dd02c01c36
|
@ -28,6 +28,7 @@
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace boost::msm;
|
using namespace boost::msm;
|
||||||
|
@ -299,6 +300,10 @@ struct Machine_ : public state_machine_def<Machine_>
|
||||||
fWorkDoneCV.notify_one();
|
fWorkDoneCV.notify_one();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fState == State::Error) {
|
||||||
|
throw StateMachine::ErrorStateException("Device transitioned to error state");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// replaces the default no-transition response.
|
// replaces the default no-transition response.
|
||||||
|
@ -463,7 +468,11 @@ void StateMachine::ProcessWork()
|
||||||
try {
|
try {
|
||||||
fsm->CallStateChangeCallbacks(State::Idle);
|
fsm->CallStateChangeCallbacks(State::Idle);
|
||||||
fsm->ProcessWork();
|
fsm->ProcessWork();
|
||||||
|
} catch(ErrorStateException& ese) {
|
||||||
|
LOG(trace) << "ErrorStateException caught in ProcessWork(), rethrowing";
|
||||||
|
throw;
|
||||||
} catch(...) {
|
} catch(...) {
|
||||||
|
LOG(debug) << "Exception caught in ProcessWork(), going to Error state and rethrowing";
|
||||||
{
|
{
|
||||||
lock_guard<mutex> lock(fsm->fStateMtx);
|
lock_guard<mutex> lock(fsm->fStateMtx);
|
||||||
fsm->fState = State::Error;
|
fsm->fState = State::Error;
|
||||||
|
@ -480,3 +489,4 @@ string StateMachine::GetStateName(const State state) { return stateNames.at(stat
|
||||||
string StateMachine::GetTransitionName(const Transition transition) { return transitionNames.at(static_cast<int>(transition)); }
|
string StateMachine::GetTransitionName(const Transition transition) { return transitionNames.at(static_cast<int>(transition)); }
|
||||||
State StateMachine::GetState(const string& state) { return stateNumbers.at(state); }
|
State StateMachine::GetState(const string& state) { return stateNumbers.at(state); }
|
||||||
Transition StateMachine::GetTransition(const string& transition) { return transitionNumbers.at(transition); }
|
Transition StateMachine::GetTransition(const string& transition) { return transitionNumbers.at(transition); }
|
||||||
|
|
||||||
|
|
|
@ -94,6 +94,8 @@ class StateMachine
|
||||||
static State GetState(const std::string& state);
|
static State GetState(const std::string& state);
|
||||||
static Transition GetTransition(const std::string& transition);
|
static Transition GetTransition(const std::string& transition);
|
||||||
|
|
||||||
|
struct ErrorStateException : std::runtime_error { using std::runtime_error::runtime_error; };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<void> fFsm;
|
std::shared_ptr<void> fFsm;
|
||||||
};
|
};
|
||||||
|
|
|
@ -29,8 +29,7 @@ namespace
|
||||||
++gSignalCount;
|
++gSignalCount;
|
||||||
gLastSignal = signal;
|
gLastSignal = signal;
|
||||||
|
|
||||||
if (gSignalCount > 1)
|
if (gSignalCount > 1) {
|
||||||
{
|
|
||||||
std::abort();
|
std::abort();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,11 +56,18 @@ Control::Control(const string& name, const Plugin::Version version, const string
|
||||||
{
|
{
|
||||||
SubscribeToDeviceStateChange([&](DeviceState newState) {
|
SubscribeToDeviceStateChange([&](DeviceState newState) {
|
||||||
LOG(trace) << "control plugin notified on new state: " << newState;
|
LOG(trace) << "control plugin notified on new state: " << newState;
|
||||||
|
|
||||||
{
|
{
|
||||||
lock_guard<mutex> lock{fEventsMutex};
|
lock_guard<mutex> lock{fEventsMutex};
|
||||||
fEvents.push(newState);
|
fEvents.push(newState);
|
||||||
}
|
}
|
||||||
fNewEvent.notify_one();
|
fNewEvent.notify_one();
|
||||||
|
|
||||||
|
if (newState == DeviceState::Error) {
|
||||||
|
fPluginShutdownRequested = true;
|
||||||
|
fDeviceShutdownRequested = true;
|
||||||
|
// throw DeviceErrorState("Controlled device transitioned to error state.");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -110,6 +116,25 @@ auto Control::RunStartupSequence() -> void
|
||||||
while (WaitForNextState() != DeviceState::Running) {}
|
while (WaitForNextState() != DeviceState::Running) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto Control::WaitForNextState() -> DeviceState
|
||||||
|
{
|
||||||
|
unique_lock<mutex> lock{fEventsMutex};
|
||||||
|
while (fEvents.empty()) {
|
||||||
|
fNewEvent.wait_for(lock, chrono::milliseconds(50));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = fEvents.front();
|
||||||
|
|
||||||
|
if (result == DeviceState::Error) {
|
||||||
|
ReleaseDeviceControl();
|
||||||
|
throw DeviceErrorState("Controlled device transitioned to error state.");
|
||||||
|
}
|
||||||
|
|
||||||
|
fEvents.pop();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
auto ControlPluginProgramOptions() -> Plugin::ProgOptions
|
auto ControlPluginProgramOptions() -> Plugin::ProgOptions
|
||||||
{
|
{
|
||||||
namespace po = boost::program_options;
|
namespace po = boost::program_options;
|
||||||
|
@ -248,6 +273,7 @@ try {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GetCurrentDeviceState() == DeviceState::Error) {
|
if (GetCurrentDeviceState() == DeviceState::Error) {
|
||||||
|
ReleaseDeviceControl();
|
||||||
throw DeviceErrorState("Controlled device transitioned to error state.");
|
throw DeviceErrorState("Controlled device transitioned to error state.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -363,64 +389,38 @@ void Control::PrintStateMachine()
|
||||||
cout << ss.str() << flush;
|
cout << ss.str() << flush;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Control::WaitForNextState() -> DeviceState
|
|
||||||
{
|
|
||||||
unique_lock<mutex> lock{fEventsMutex};
|
|
||||||
while (fEvents.empty()) {
|
|
||||||
fNewEvent.wait_for(lock, chrono::milliseconds(50));
|
|
||||||
}
|
|
||||||
|
|
||||||
auto result = fEvents.front();
|
|
||||||
|
|
||||||
if (result == DeviceState::Error) {
|
|
||||||
throw DeviceErrorState("Controlled device transitioned to error state.");
|
|
||||||
}
|
|
||||||
|
|
||||||
fEvents.pop();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Control::StaticMode() -> void
|
auto Control::StaticMode() -> void
|
||||||
try
|
try {
|
||||||
{
|
|
||||||
RunStartupSequence();
|
RunStartupSequence();
|
||||||
|
|
||||||
{
|
{
|
||||||
// Wait for next state, which is DeviceState::Ready,
|
// Wait for next state, which is DeviceState::Ready,
|
||||||
// or for device shutdown request (Ctrl-C)
|
// or for device shutdown request (Ctrl-C)
|
||||||
unique_lock<mutex> lock{fEventsMutex};
|
unique_lock<mutex> lock{fEventsMutex};
|
||||||
while (fEvents.empty() && !fDeviceShutdownRequested)
|
while (fEvents.empty() && !fDeviceShutdownRequested) {
|
||||||
{
|
|
||||||
fNewEvent.wait_for(lock, chrono::milliseconds(50));
|
fNewEvent.wait_for(lock, chrono::milliseconds(50));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fEvents.front() == DeviceState::Error)
|
if (fEvents.front() == DeviceState::Error) {
|
||||||
{
|
ReleaseDeviceControl();
|
||||||
throw DeviceErrorState("Controlled device transitioned to error state.");
|
throw DeviceErrorState("Controlled device transitioned to error state.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RunShutdownSequence();
|
RunShutdownSequence();
|
||||||
}
|
} catch (PluginServices::DeviceControlError& e) {
|
||||||
catch (PluginServices::DeviceControlError& e)
|
|
||||||
{
|
|
||||||
// If we are here, it means another plugin has taken control. That's fine, just print the exception message and do nothing else.
|
// If we are here, it means another plugin has taken control. That's fine, just print the exception message and do nothing else.
|
||||||
LOG(debug) << e.what();
|
LOG(debug) << e.what();
|
||||||
}
|
} catch (DeviceErrorState&) {
|
||||||
catch (DeviceErrorState&)
|
|
||||||
{
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Control::SignalHandler() -> void
|
auto Control::SignalHandler() -> void
|
||||||
{
|
{
|
||||||
while (gSignalCount == 0 && !fPluginShutdownRequested)
|
while (gSignalCount == 0 && !fPluginShutdownRequested) {
|
||||||
{
|
|
||||||
this_thread::sleep_for(chrono::milliseconds(100));
|
this_thread::sleep_for(chrono::milliseconds(100));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fPluginShutdownRequested)
|
if (!fPluginShutdownRequested) {
|
||||||
{
|
|
||||||
LOG(info) << "Received device shutdown request (signal " << gLastSignal << ").";
|
LOG(info) << "Received device shutdown request (signal " << gLastSignal << ").";
|
||||||
LOG(info) << "Waiting for graceful device shutdown. Hit Ctrl-C again to abort immediately.";
|
LOG(info) << "Waiting for graceful device shutdown. Hit Ctrl-C again to abort immediately.";
|
||||||
|
|
||||||
|
@ -431,20 +431,14 @@ auto Control::SignalHandler() -> void
|
||||||
if (fControllerThread.joinable()) fControllerThread.join();
|
if (fControllerThread.joinable()) fControllerThread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fDeviceHasShutdown)
|
if (!fDeviceHasShutdown) {
|
||||||
{
|
|
||||||
// Take over control and attempt graceful shutdown
|
// Take over control and attempt graceful shutdown
|
||||||
StealDeviceControl();
|
StealDeviceControl();
|
||||||
try
|
try {
|
||||||
{
|
|
||||||
RunShutdownSequence();
|
RunShutdownSequence();
|
||||||
}
|
} catch (PluginServices::DeviceControlError& e) {
|
||||||
catch (PluginServices::DeviceControlError& e)
|
|
||||||
{
|
|
||||||
LOG(info) << "Graceful device shutdown failed: " << e.what() << " If hanging, hit Ctrl-C again to abort immediately.";
|
LOG(info) << "Graceful device shutdown failed: " << e.what() << " If hanging, hit Ctrl-C again to abort immediately.";
|
||||||
}
|
} catch (...) {
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
LOG(info) << "Graceful device shutdown failed. If hanging, hit Ctrl-C again to abort immediately.";
|
LOG(info) << "Graceful device shutdown failed. If hanging, hit Ctrl-C again to abort immediately.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,6 +105,8 @@ add_testsuite(Device
|
||||||
device/_config.cxx
|
device/_config.cxx
|
||||||
device/_waitfor.cxx
|
device/_waitfor.cxx
|
||||||
device/_exceptions.cxx
|
device/_exceptions.cxx
|
||||||
|
device/_error_state.cxx
|
||||||
|
device/_signals.cxx
|
||||||
|
|
||||||
LINKS FairMQ
|
LINKS FairMQ
|
||||||
DEPENDS testhelper_runTestDevice
|
DEPENDS testhelper_runTestDevice
|
||||||
|
|
149
test/device/_error_state.cxx
Normal file
149
test/device/_error_state.cxx
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
/********************************************************************************
|
||||||
|
* Copyright (C) 2018 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH *
|
||||||
|
* *
|
||||||
|
* This software is distributed under the terms of the *
|
||||||
|
* GNU Lesser General Public Licence (LGPL) version 3, *
|
||||||
|
* copied verbatim in the file "LICENSE" *
|
||||||
|
********************************************************************************/
|
||||||
|
|
||||||
|
#include "runner.h"
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <boost/process.hpp>
|
||||||
|
#include <fairmq/Tools.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace fair::mq::test;
|
||||||
|
using namespace fair::mq::tools;
|
||||||
|
|
||||||
|
void RunErrorStateIn(const std::string& state, const std::string& input = "")
|
||||||
|
{
|
||||||
|
size_t session{fair::mq::tools::UuidHash()};
|
||||||
|
|
||||||
|
execute_result result{"", 100};
|
||||||
|
thread device_thread([&]() {
|
||||||
|
stringstream cmd;
|
||||||
|
cmd << runTestDevice
|
||||||
|
<< " --id error_state_" << state << "_"
|
||||||
|
<< " --control " << ((input == "") ? "static" : "interactive")
|
||||||
|
<< " --session " << session
|
||||||
|
<< " --color false";
|
||||||
|
result = execute(cmd.str(), "[ErrorFound IN " + state + "]", input);
|
||||||
|
});
|
||||||
|
|
||||||
|
device_thread.join();
|
||||||
|
|
||||||
|
ASSERT_NE(std::string::npos, result.console_out.find("going to change to Error state from " + state + "()"));
|
||||||
|
|
||||||
|
exit(result.exit_code);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ErrorState, static_InInit)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunErrorStateIn("Init"), ::testing::ExitedWithCode(1), "");
|
||||||
|
}
|
||||||
|
TEST(ErrorState, static_InBind)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunErrorStateIn("Bind"), ::testing::ExitedWithCode(1), "");
|
||||||
|
}
|
||||||
|
TEST(ErrorState, static_InConnect)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunErrorStateIn("Connect"), ::testing::ExitedWithCode(1), "");
|
||||||
|
}
|
||||||
|
TEST(ErrorState, static_InInitTask)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunErrorStateIn("InitTask"), ::testing::ExitedWithCode(1), "");
|
||||||
|
}
|
||||||
|
TEST(ErrorState, static_InPreRun)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunErrorStateIn("PreRun"), ::testing::ExitedWithCode(1), "");
|
||||||
|
}
|
||||||
|
TEST(ErrorState, static_InRun)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunErrorStateIn("Run"), ::testing::ExitedWithCode(1), "");
|
||||||
|
}
|
||||||
|
TEST(ErrorState, static_InPostRun)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunErrorStateIn("PostRun"), ::testing::ExitedWithCode(1), "");
|
||||||
|
}
|
||||||
|
TEST(ErrorState, static_InResetTask)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunErrorStateIn("ResetTask"), ::testing::ExitedWithCode(1), "");
|
||||||
|
}
|
||||||
|
TEST(ErrorState, static_InReset)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunErrorStateIn("Reset"), ::testing::ExitedWithCode(1), "");
|
||||||
|
}
|
||||||
|
TEST(ErrorState, interactive_InInit)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunErrorStateIn("Init", "q"), ::testing::ExitedWithCode(1), "");
|
||||||
|
}
|
||||||
|
TEST(ErrorState, interactive_InBind)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunErrorStateIn("Bind", "q"), ::testing::ExitedWithCode(1), "");
|
||||||
|
}
|
||||||
|
TEST(ErrorState, interactive_InConnect)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunErrorStateIn("Connect", "q"), ::testing::ExitedWithCode(1), "");
|
||||||
|
}
|
||||||
|
TEST(ErrorState, interactive_InInitTask)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunErrorStateIn("InitTask", "q"), ::testing::ExitedWithCode(1), "");
|
||||||
|
}
|
||||||
|
TEST(ErrorState, interactive_InPreRun)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunErrorStateIn("PreRun", "q"), ::testing::ExitedWithCode(1), "");
|
||||||
|
}
|
||||||
|
TEST(ErrorState, interactive_InRun)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunErrorStateIn("Run", "q"), ::testing::ExitedWithCode(1), "");
|
||||||
|
}
|
||||||
|
TEST(ErrorState, interactive_InPostRun)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunErrorStateIn("PostRun", "q"), ::testing::ExitedWithCode(1), "");
|
||||||
|
}
|
||||||
|
TEST(ErrorState, interactive_InResetTask)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunErrorStateIn("ResetTask", "q"), ::testing::ExitedWithCode(1), "");
|
||||||
|
}
|
||||||
|
TEST(ErrorState, interactive_InReset)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunErrorStateIn("Reset", "q"), ::testing::ExitedWithCode(1), "");
|
||||||
|
}
|
||||||
|
TEST(ErrorState, interactive_invalid_InInit)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunErrorStateIn("Init", "_"), ::testing::ExitedWithCode(1), "");
|
||||||
|
}
|
||||||
|
TEST(ErrorState, interactive_invalid_InBind)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunErrorStateIn("Bind", "_"), ::testing::ExitedWithCode(1), "");
|
||||||
|
}
|
||||||
|
TEST(ErrorState, interactive_invalid_InConnect)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunErrorStateIn("Connect", "_"), ::testing::ExitedWithCode(1), "");
|
||||||
|
}
|
||||||
|
TEST(ErrorState, interactive_invalid_InInitTask)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunErrorStateIn("InitTask", "_"), ::testing::ExitedWithCode(1), "");
|
||||||
|
}
|
||||||
|
TEST(ErrorState, interactive_invalid_InPreRun)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunErrorStateIn("PreRun", "_"), ::testing::ExitedWithCode(1), "");
|
||||||
|
}
|
||||||
|
TEST(ErrorState, interactive_invalid_InRun)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunErrorStateIn("Run", "_"), ::testing::ExitedWithCode(1), "");
|
||||||
|
}
|
||||||
|
TEST(ErrorState, interactive_invalid_InPostRun)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunErrorStateIn("PostRun", "_"), ::testing::ExitedWithCode(1), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
|
@ -49,6 +49,14 @@ TEST(Exceptions, static_InInit)
|
||||||
{
|
{
|
||||||
EXPECT_EXIT(RunExceptionIn("Init"), ::testing::ExitedWithCode(1), "");
|
EXPECT_EXIT(RunExceptionIn("Init"), ::testing::ExitedWithCode(1), "");
|
||||||
}
|
}
|
||||||
|
TEST(Exceptions, static_InBind)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunExceptionIn("Bind"), ::testing::ExitedWithCode(1), "");
|
||||||
|
}
|
||||||
|
TEST(Exceptions, static_InConnect)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunExceptionIn("Connect"), ::testing::ExitedWithCode(1), "");
|
||||||
|
}
|
||||||
TEST(Exceptions, static_InInitTask)
|
TEST(Exceptions, static_InInitTask)
|
||||||
{
|
{
|
||||||
EXPECT_EXIT(RunExceptionIn("InitTask"), ::testing::ExitedWithCode(1), "");
|
EXPECT_EXIT(RunExceptionIn("InitTask"), ::testing::ExitedWithCode(1), "");
|
||||||
|
@ -77,6 +85,14 @@ TEST(Exceptions, interactive_InInit)
|
||||||
{
|
{
|
||||||
EXPECT_EXIT(RunExceptionIn("Init", "q"), ::testing::ExitedWithCode(1), "");
|
EXPECT_EXIT(RunExceptionIn("Init", "q"), ::testing::ExitedWithCode(1), "");
|
||||||
}
|
}
|
||||||
|
TEST(Exceptions, interactive_InBind)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunExceptionIn("Bind", "q"), ::testing::ExitedWithCode(1), "");
|
||||||
|
}
|
||||||
|
TEST(Exceptions, interactive_InConnect)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunExceptionIn("Connect", "q"), ::testing::ExitedWithCode(1), "");
|
||||||
|
}
|
||||||
TEST(Exceptions, interactive_InInitTask)
|
TEST(Exceptions, interactive_InInitTask)
|
||||||
{
|
{
|
||||||
EXPECT_EXIT(RunExceptionIn("InitTask", "q"), ::testing::ExitedWithCode(1), "");
|
EXPECT_EXIT(RunExceptionIn("InitTask", "q"), ::testing::ExitedWithCode(1), "");
|
||||||
|
@ -105,6 +121,14 @@ TEST(Exceptions, interactive_invalid_InInit)
|
||||||
{
|
{
|
||||||
EXPECT_EXIT(RunExceptionIn("Init", "_"), ::testing::ExitedWithCode(1), "");
|
EXPECT_EXIT(RunExceptionIn("Init", "_"), ::testing::ExitedWithCode(1), "");
|
||||||
}
|
}
|
||||||
|
TEST(Exceptions, interactive_invalid_InBind)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunExceptionIn("Bind", "_"), ::testing::ExitedWithCode(1), "");
|
||||||
|
}
|
||||||
|
TEST(Exceptions, interactive_invalid_InConnect)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunExceptionIn("Connect", "_"), ::testing::ExitedWithCode(1), "");
|
||||||
|
}
|
||||||
TEST(Exceptions, interactive_invalid_InInitTask)
|
TEST(Exceptions, interactive_invalid_InInitTask)
|
||||||
{
|
{
|
||||||
EXPECT_EXIT(RunExceptionIn("InitTask", "_"), ::testing::ExitedWithCode(1), "");
|
EXPECT_EXIT(RunExceptionIn("InitTask", "_"), ::testing::ExitedWithCode(1), "");
|
||||||
|
|
149
test/device/_signals.cxx
Normal file
149
test/device/_signals.cxx
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
/********************************************************************************
|
||||||
|
* Copyright (C) 2018 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH *
|
||||||
|
* *
|
||||||
|
* This software is distributed under the terms of the *
|
||||||
|
* GNU Lesser General Public Licence (LGPL) version 3, *
|
||||||
|
* copied verbatim in the file "LICENSE" *
|
||||||
|
********************************************************************************/
|
||||||
|
|
||||||
|
#include "runner.h"
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <boost/process.hpp>
|
||||||
|
#include <fairmq/Tools.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace fair::mq::test;
|
||||||
|
using namespace fair::mq::tools;
|
||||||
|
|
||||||
|
void RunSignalIn(const std::string& state, const std::string& input = "")
|
||||||
|
{
|
||||||
|
size_t session{fair::mq::tools::UuidHash()};
|
||||||
|
|
||||||
|
execute_result result{"", 100};
|
||||||
|
thread device_thread([&]() {
|
||||||
|
stringstream cmd;
|
||||||
|
cmd << runTestDevice
|
||||||
|
<< " --id signals_" << state << "_"
|
||||||
|
<< " --control " << ((input == "") ? "static" : "interactive")
|
||||||
|
<< " --session " << session
|
||||||
|
<< " --color false";
|
||||||
|
result = execute(cmd.str(), "[SIGINT IN " + state + "]", input);
|
||||||
|
});
|
||||||
|
|
||||||
|
device_thread.join();
|
||||||
|
|
||||||
|
ASSERT_NE(std::string::npos, result.console_out.find("raising SIGINT from " + state + "()"));
|
||||||
|
|
||||||
|
exit(result.exit_code);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Signal_SIGINT, static_InInit)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunSignalIn("Init"), ::testing::ExitedWithCode(0), "");
|
||||||
|
}
|
||||||
|
TEST(Signal_SIGINT, static_InBind)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunSignalIn("Bind"), ::testing::ExitedWithCode(0), "");
|
||||||
|
}
|
||||||
|
TEST(Signal_SIGINT, static_InConnect)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunSignalIn("Connect"), ::testing::ExitedWithCode(0), "");
|
||||||
|
}
|
||||||
|
TEST(Signal_SIGINT, static_InInitTask)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunSignalIn("InitTask"), ::testing::ExitedWithCode(0), "");
|
||||||
|
}
|
||||||
|
TEST(Signal_SIGINT, static_InPreRun)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunSignalIn("PreRun"), ::testing::ExitedWithCode(0), "");
|
||||||
|
}
|
||||||
|
TEST(Signal_SIGINT, static_InRun)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunSignalIn("Run"), ::testing::ExitedWithCode(0), "");
|
||||||
|
}
|
||||||
|
TEST(Signal_SIGINT, static_InPostRun)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunSignalIn("PostRun"), ::testing::ExitedWithCode(0), "");
|
||||||
|
}
|
||||||
|
TEST(Signal_SIGINT, static_InResetTask)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunSignalIn("ResetTask"), ::testing::ExitedWithCode(0), "");
|
||||||
|
}
|
||||||
|
TEST(Signal_SIGINT, static_InReset)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunSignalIn("Reset"), ::testing::ExitedWithCode(0), "");
|
||||||
|
}
|
||||||
|
TEST(Signal_SIGINT, interactive_InInit)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunSignalIn("Init", "q"), ::testing::ExitedWithCode(0), "");
|
||||||
|
}
|
||||||
|
TEST(Signal_SIGINT, interactive_InBind)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunSignalIn("Bind", "q"), ::testing::ExitedWithCode(0), "");
|
||||||
|
}
|
||||||
|
TEST(Signal_SIGINT, interactive_InConnect)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunSignalIn("Connect", "q"), ::testing::ExitedWithCode(0), "");
|
||||||
|
}
|
||||||
|
TEST(Signal_SIGINT, interactive_InInitTask)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunSignalIn("InitTask", "q"), ::testing::ExitedWithCode(0), "");
|
||||||
|
}
|
||||||
|
TEST(Signal_SIGINT, interactive_InPreRun)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunSignalIn("PreRun", "q"), ::testing::ExitedWithCode(0), "");
|
||||||
|
}
|
||||||
|
TEST(Signal_SIGINT, interactive_InRun)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunSignalIn("Run", "q"), ::testing::ExitedWithCode(0), "");
|
||||||
|
}
|
||||||
|
TEST(Signal_SIGINT, interactive_InPostRun)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunSignalIn("PostRun", "q"), ::testing::ExitedWithCode(0), "");
|
||||||
|
}
|
||||||
|
TEST(Signal_SIGINT, interactive_InResetTask)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunSignalIn("ResetTask", "q"), ::testing::ExitedWithCode(0), "");
|
||||||
|
}
|
||||||
|
TEST(Signal_SIGINT, interactive_InReset)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunSignalIn("Reset", "q"), ::testing::ExitedWithCode(0), "");
|
||||||
|
}
|
||||||
|
TEST(Signal_SIGINT, interactive_invalid_InInit)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunSignalIn("Init", "_"), ::testing::ExitedWithCode(0), "");
|
||||||
|
}
|
||||||
|
TEST(Signal_SIGINT, interactive_invalid_InBind)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunSignalIn("Bind", "_"), ::testing::ExitedWithCode(0), "");
|
||||||
|
}
|
||||||
|
TEST(Signal_SIGINT, interactive_invalid_InConnect)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunSignalIn("Connect", "_"), ::testing::ExitedWithCode(0), "");
|
||||||
|
}
|
||||||
|
TEST(Signal_SIGINT, interactive_invalid_InInitTask)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunSignalIn("InitTask", "_"), ::testing::ExitedWithCode(0), "");
|
||||||
|
}
|
||||||
|
TEST(Signal_SIGINT, interactive_invalid_InPreRun)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunSignalIn("PreRun", "_"), ::testing::ExitedWithCode(0), "");
|
||||||
|
}
|
||||||
|
TEST(Signal_SIGINT, interactive_invalid_InRun)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunSignalIn("Run", "_"), ::testing::ExitedWithCode(0), "");
|
||||||
|
}
|
||||||
|
TEST(Signal_SIGINT, interactive_invalid_InPostRun)
|
||||||
|
{
|
||||||
|
EXPECT_EXIT(RunSignalIn("PostRun", "_"), ::testing::ExitedWithCode(0), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
111
test/helper/devices/TestErrorState.h
Normal file
111
test/helper/devices/TestErrorState.h
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
/********************************************************************************
|
||||||
|
* Copyright (C) 2018 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH *
|
||||||
|
* *
|
||||||
|
* This software is distributed under the terms of the *
|
||||||
|
* GNU Lesser General Public Licence (LGPL) version 3, *
|
||||||
|
* copied verbatim in the file "LICENSE" *
|
||||||
|
********************************************************************************/
|
||||||
|
|
||||||
|
#ifndef FAIR_MQ_TEST_ERROR_STATE_H
|
||||||
|
#define FAIR_MQ_TEST_ERROR_STATE_H
|
||||||
|
|
||||||
|
#include <FairMQDevice.h>
|
||||||
|
#include <FairMQLogger.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace fair
|
||||||
|
{
|
||||||
|
namespace mq
|
||||||
|
{
|
||||||
|
namespace test
|
||||||
|
{
|
||||||
|
|
||||||
|
class ErrorState : public FairMQDevice
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void Init() override
|
||||||
|
{
|
||||||
|
std::string state("Init");
|
||||||
|
if (std::string::npos != GetId().find("_" + state + "_")) {
|
||||||
|
LOG(debug) << "going to change to Error state from " << state << "()";
|
||||||
|
ChangeState(fair::mq::Transition::ErrorFound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void Bind() override
|
||||||
|
{
|
||||||
|
std::string state("Bind");
|
||||||
|
if (std::string::npos != GetId().find("_" + state + "_")) {
|
||||||
|
LOG(debug) << "going to change to Error state from " << state << "()";
|
||||||
|
ChangeState(fair::mq::Transition::ErrorFound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void Connect() override
|
||||||
|
{
|
||||||
|
std::string state("Connect");
|
||||||
|
if (std::string::npos != GetId().find("_" + state + "_")) {
|
||||||
|
LOG(debug) << "going to change to Error state from " << state << "()";
|
||||||
|
ChangeState(fair::mq::Transition::ErrorFound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitTask() override
|
||||||
|
{
|
||||||
|
std::string state("InitTask");
|
||||||
|
if (std::string::npos != GetId().find("_" + state + "_")) {
|
||||||
|
LOG(debug) << "going to change to Error state from " << state << "()";
|
||||||
|
ChangeState(fair::mq::Transition::ErrorFound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PreRun() override
|
||||||
|
{
|
||||||
|
std::string state("PreRun");
|
||||||
|
if (std::string::npos != GetId().find("_" + state + "_")) {
|
||||||
|
LOG(debug) << "going to change to Error state from " << state << "()";
|
||||||
|
ChangeState(fair::mq::Transition::ErrorFound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Run() override
|
||||||
|
{
|
||||||
|
std::string state("Run");
|
||||||
|
if (std::string::npos != GetId().find("_" + state + "_")) {
|
||||||
|
LOG(debug) << "going to change to Error state from " << state << "()";
|
||||||
|
ChangeState(fair::mq::Transition::ErrorFound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PostRun() override
|
||||||
|
{
|
||||||
|
std::string state("PostRun");
|
||||||
|
if (std::string::npos != GetId().find("_" + state + "_")) {
|
||||||
|
LOG(debug) << "going to change to Error state from " << state << "()";
|
||||||
|
ChangeState(fair::mq::Transition::ErrorFound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResetTask() override
|
||||||
|
{
|
||||||
|
std::string state("ResetTask");
|
||||||
|
if (std::string::npos != GetId().find("_" + state + "_")) {
|
||||||
|
LOG(debug) << "going to change to Error state from " << state << "()";
|
||||||
|
ChangeState(fair::mq::Transition::ErrorFound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Reset() override
|
||||||
|
{
|
||||||
|
std::string state("Reset");
|
||||||
|
if (std::string::npos != GetId().find("_" + state + "_")) {
|
||||||
|
LOG(debug) << "going to change to Error state from " << state << "()";
|
||||||
|
ChangeState(fair::mq::Transition::ErrorFound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace test
|
||||||
|
} // namespace mq
|
||||||
|
} // namespace fair
|
||||||
|
|
||||||
|
#endif /* FAIR_MQ_TEST_ERROR_STATE_H */
|
|
@ -32,6 +32,20 @@ class Exceptions : public FairMQDevice
|
||||||
throw std::runtime_error("exception in " + state + "()");
|
throw std::runtime_error("exception in " + state + "()");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
auto Bind() -> void override
|
||||||
|
{
|
||||||
|
std::string state("Bind");
|
||||||
|
if (std::string::npos != GetId().find("_" + state + "_")) {
|
||||||
|
throw std::runtime_error("exception in " + state + "()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto Connect() -> void override
|
||||||
|
{
|
||||||
|
std::string state("Connect");
|
||||||
|
if (std::string::npos != GetId().find("_" + state + "_")) {
|
||||||
|
throw std::runtime_error("exception in " + state + "()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto InitTask() -> void override
|
auto InitTask() -> void override
|
||||||
{
|
{
|
||||||
|
|
112
test/helper/devices/TestSignals.h
Normal file
112
test/helper/devices/TestSignals.h
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
/********************************************************************************
|
||||||
|
* Copyright (C) 2018 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH *
|
||||||
|
* *
|
||||||
|
* This software is distributed under the terms of the *
|
||||||
|
* GNU Lesser General Public Licence (LGPL) version 3, *
|
||||||
|
* copied verbatim in the file "LICENSE" *
|
||||||
|
********************************************************************************/
|
||||||
|
|
||||||
|
#ifndef FAIR_MQ_TEST_SIGNALS_H
|
||||||
|
#define FAIR_MQ_TEST_SIGNALS_H
|
||||||
|
|
||||||
|
#include <FairMQDevice.h>
|
||||||
|
#include <FairMQLogger.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <csignal>
|
||||||
|
|
||||||
|
namespace fair
|
||||||
|
{
|
||||||
|
namespace mq
|
||||||
|
{
|
||||||
|
namespace test
|
||||||
|
{
|
||||||
|
|
||||||
|
class Signals : public FairMQDevice
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void Init() override
|
||||||
|
{
|
||||||
|
std::string state("Init");
|
||||||
|
if (std::string::npos != GetId().find("_" + state + "_")) {
|
||||||
|
LOG(debug) << "raising SIGINT from " << state << "()";
|
||||||
|
raise(SIGINT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void Bind() override
|
||||||
|
{
|
||||||
|
std::string state("Bind");
|
||||||
|
if (std::string::npos != GetId().find("_" + state + "_")) {
|
||||||
|
LOG(debug) << "raising SIGINT from " << state << "()";
|
||||||
|
raise(SIGINT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void Connect() override
|
||||||
|
{
|
||||||
|
std::string state("Connect");
|
||||||
|
if (std::string::npos != GetId().find("_" + state + "_")) {
|
||||||
|
LOG(debug) << "raising SIGINT from " << state << "()";
|
||||||
|
raise(SIGINT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitTask() override
|
||||||
|
{
|
||||||
|
std::string state("InitTask");
|
||||||
|
if (std::string::npos != GetId().find("_" + state + "_")) {
|
||||||
|
LOG(debug) << "raising SIGINT from " << state << "()";
|
||||||
|
raise(SIGINT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PreRun() override
|
||||||
|
{
|
||||||
|
std::string state("PreRun");
|
||||||
|
if (std::string::npos != GetId().find("_" + state + "_")) {
|
||||||
|
LOG(debug) << "raising SIGINT from " << state << "()";
|
||||||
|
raise(SIGINT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Run() override
|
||||||
|
{
|
||||||
|
std::string state("Run");
|
||||||
|
if (std::string::npos != GetId().find("_" + state + "_")) {
|
||||||
|
LOG(debug) << "raising SIGINT from " << state << "()";
|
||||||
|
raise(SIGINT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PostRun() override
|
||||||
|
{
|
||||||
|
std::string state("PostRun");
|
||||||
|
if (std::string::npos != GetId().find("_" + state + "_")) {
|
||||||
|
LOG(debug) << "raising SIGINT from " << state << "()";
|
||||||
|
raise(SIGINT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResetTask() override
|
||||||
|
{
|
||||||
|
std::string state("ResetTask");
|
||||||
|
if (std::string::npos != GetId().find("_" + state + "_")) {
|
||||||
|
LOG(debug) << "raising SIGINT from " << state << "()";
|
||||||
|
raise(SIGINT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Reset() override
|
||||||
|
{
|
||||||
|
std::string state("Reset");
|
||||||
|
if (std::string::npos != GetId().find("_" + state + "_")) {
|
||||||
|
LOG(debug) << "raising SIGINT from " << state << "()";
|
||||||
|
raise(SIGINT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace test
|
||||||
|
} // namespace mq
|
||||||
|
} // namespace fair
|
||||||
|
|
||||||
|
#endif /* FAIR_MQ_TEST_SIGNALS_H */
|
|
@ -19,6 +19,8 @@
|
||||||
#include "devices/TestTransferTimeout.h"
|
#include "devices/TestTransferTimeout.h"
|
||||||
#include "devices/TestWaitFor.h"
|
#include "devices/TestWaitFor.h"
|
||||||
#include "devices/TestExceptions.h"
|
#include "devices/TestExceptions.h"
|
||||||
|
#include "devices/TestErrorState.h"
|
||||||
|
#include "devices/TestSignals.h"
|
||||||
|
|
||||||
#include <runFairMQDevice.h>
|
#include <runFairMQDevice.h>
|
||||||
|
|
||||||
|
@ -40,60 +42,38 @@ auto getDevice(const FairMQProgOptions& config) -> FairMQDevicePtr
|
||||||
using namespace fair::mq::test;
|
using namespace fair::mq::test;
|
||||||
|
|
||||||
auto id = config.GetValue<std::string>("id");
|
auto id = config.GetValue<std::string>("id");
|
||||||
if (0 == id.find("pull_"))
|
|
||||||
{
|
if (0 == id.find("pull_")) {
|
||||||
return new Pull;
|
return new Pull;
|
||||||
}
|
} else if (0 == id.find("push_")) {
|
||||||
else if (0 == id.find("push_"))
|
|
||||||
{
|
|
||||||
return new Push;
|
return new Push;
|
||||||
}
|
} else if (0 == id.find("sub_")) {
|
||||||
else if (0 == id.find("sub_"))
|
|
||||||
{
|
|
||||||
return new Sub;
|
return new Sub;
|
||||||
}
|
} else if (0 == id.find("pub_")) {
|
||||||
else if (0 == id.find("pub_"))
|
|
||||||
{
|
|
||||||
return new Pub;
|
return new Pub;
|
||||||
}
|
} else if (0 == id.find("req_")) {
|
||||||
else if (0 == id.find("req_"))
|
|
||||||
{
|
|
||||||
return new Req;
|
return new Req;
|
||||||
}
|
} else if (0 == id.find("rep_")) {
|
||||||
else if (0 == id.find("rep_"))
|
|
||||||
{
|
|
||||||
return new Rep;
|
return new Rep;
|
||||||
}
|
} else if (0 == id.find("transfer_timeout_")) {
|
||||||
else if (0 == id.find("transfer_timeout_"))
|
|
||||||
{
|
|
||||||
return new TransferTimeout;
|
return new TransferTimeout;
|
||||||
}
|
} else if (0 == id.find("pollout_")) {
|
||||||
else if (0 == id.find("pollout_"))
|
|
||||||
{
|
|
||||||
return new PollOut;
|
return new PollOut;
|
||||||
}
|
} else if (0 == id.find("pollin_")) {
|
||||||
else if (0 == id.find("pollin_"))
|
|
||||||
{
|
|
||||||
return new PollIn;
|
return new PollIn;
|
||||||
}
|
} else if (0 == id.find("pairleft_")) {
|
||||||
else if (0 == id.find("pairleft_"))
|
|
||||||
{
|
|
||||||
return new PairLeft;
|
return new PairLeft;
|
||||||
}
|
} else if (0 == id.find("pairright_")) {
|
||||||
else if (0 == id.find("pairright_"))
|
|
||||||
{
|
|
||||||
return new PairRight;
|
return new PairRight;
|
||||||
}
|
} else if (0 == id.find("waitfor_")) {
|
||||||
else if (0 == id.find("waitfor_"))
|
|
||||||
{
|
|
||||||
return new TestWaitFor;
|
return new TestWaitFor;
|
||||||
}
|
} else if (0 == id.find("exceptions_")) {
|
||||||
else if (0 == id.find("exceptions_"))
|
|
||||||
{
|
|
||||||
return new Exceptions;
|
return new Exceptions;
|
||||||
}
|
} else if (0 == id.find("error_state_")) {
|
||||||
else
|
return new ErrorState;
|
||||||
{
|
} else if (0 == id.find("signals_")) {
|
||||||
|
return new Signals;
|
||||||
|
} else {
|
||||||
cerr << "Don't know id '" << id << "'" << endl;
|
cerr << "Don't know id '" << id << "'" << endl;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user