WIP state for Hugo to potentially fix a segfault
This commit is contained in:
+19
-4
@@ -1,10 +1,25 @@
|
||||
record(longin, "TEST:INT") {
|
||||
field(DESC, "Example record")
|
||||
record(longin, "MEPICSCA:TEST:LONGIN") {
|
||||
field(VAL, 42)
|
||||
field(HOPR, 100)
|
||||
field(LOPR, 0)
|
||||
field(PINI, "YES")
|
||||
}
|
||||
|
||||
record(ai, "TEST:DOUBLE") {
|
||||
field(DESC, "Example record")
|
||||
record(ai, "MEPICSCA:TEST:AI") {
|
||||
field(VAL, 84)
|
||||
}
|
||||
|
||||
record(stringin, "MEPICSCA:TEST:STRINGIN") {
|
||||
field(VAL, "MyTestString")
|
||||
}
|
||||
|
||||
record(waveform, "MEPICSCA:TEST:WAVEFORM") {
|
||||
field(FTVL, "CHAR")
|
||||
field(NELM, "200")
|
||||
}
|
||||
|
||||
record(bo, "MEPICSCA:TEST:BO") {
|
||||
field(VAL, 1)
|
||||
field(ZNAM, "OFF")
|
||||
field(ONAM, "ON")
|
||||
}
|
||||
@@ -3,3 +3,6 @@
|
||||
dbLoadRecords("$(IOCDIR)/record.db")
|
||||
|
||||
iocInit()
|
||||
|
||||
# Initialize the waveform record
|
||||
dbpf("MEPICSCA:TEST:WAVEFORM", "ABC")
|
||||
|
||||
+2
-1
@@ -7,4 +7,5 @@ export EPICS_CA_ADDR_LIST=127.0.0.1
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
export IOCDIR=$(pwd)
|
||||
./st.cmd
|
||||
|
||||
exec ./st.cmd
|
||||
|
||||
+324
-51
@@ -2,7 +2,10 @@
|
||||
#include "m_epics_ca.h"
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <iostream>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
@@ -10,7 +13,7 @@
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define CHECK(cond) \
|
||||
#define ASSERT(cond) \
|
||||
do { \
|
||||
if (!(cond)) { \
|
||||
std::cerr << "Test failed: " #cond << std::endl; \
|
||||
@@ -21,9 +24,9 @@
|
||||
#define EQUAL(left, right) \
|
||||
do { \
|
||||
if (!(left == right)) { \
|
||||
std::cerr << "\nTest failed: " << (left) << " (" << #left \
|
||||
<< ") != " << (right) << " (" << #right << *")" \
|
||||
<< std::endl; \
|
||||
std::cerr << "\nTest failed at line " << __LINE__ << ": " \
|
||||
<< (left) << " (" << #left << ") != " << (right) << " (" \
|
||||
<< #right << *")" << std::endl; \
|
||||
return 1; \
|
||||
} \
|
||||
} while (0)
|
||||
@@ -31,12 +34,24 @@
|
||||
class IocProcess {
|
||||
public:
|
||||
explicit IocProcess(const std::string &path) {
|
||||
/*
|
||||
Important note: DO NOT USE setpgid HERE!
|
||||
Even though it looks "cleaner" than the pkill solution in the destructor
|
||||
below (because you could just terminate the group), this seems to cause
|
||||
trouble deep within EPICS, causing the server not to be reachable
|
||||
anymore with caget.
|
||||
*/
|
||||
_pid = fork();
|
||||
|
||||
if (_pid == 0) {
|
||||
|
||||
// Hide IOC output
|
||||
int devnull = open("/dev/null", O_WRONLY);
|
||||
dup2(devnull, STDOUT_FILENO);
|
||||
close(devnull);
|
||||
|
||||
// Start the IOC from the child process
|
||||
setpgid(0, 0);
|
||||
execl(path.c_str(), path.c_str(), (char *)nullptr);
|
||||
execl(path.c_str(), path.c_str(), (char *)NULL);
|
||||
perror("execl failed");
|
||||
_exit(1);
|
||||
}
|
||||
@@ -44,17 +59,13 @@ class IocProcess {
|
||||
if (_pid < 0) {
|
||||
throw std::runtime_error("fork failed");
|
||||
}
|
||||
// Set the ID of the entire process group so it can be torn down in the
|
||||
// destructor later. This ensures IOC teardown.
|
||||
setpgid(_pid, _pid);
|
||||
}
|
||||
|
||||
~IocProcess() {
|
||||
if (_pid <= 0)
|
||||
return;
|
||||
|
||||
// Kill the entire process group due to the -1
|
||||
kill(-_pid, SIGTERM);
|
||||
kill(_pid, SIGTERM);
|
||||
|
||||
// Give the IOC 50 * 100 * 1000 = 5 seconds time for graceful
|
||||
// termination.
|
||||
@@ -66,25 +77,78 @@ class IocProcess {
|
||||
usleep(100 * 1000);
|
||||
}
|
||||
|
||||
// Force kill group if needed
|
||||
kill(-_pid, SIGKILL);
|
||||
waitpid(_pid, nullptr, 0);
|
||||
kill(_pid, SIGKILL);
|
||||
}
|
||||
|
||||
private:
|
||||
pid_t _pid = -1;
|
||||
};
|
||||
|
||||
void handler(int sig) { printf("Received signal: %d\n", sig); }
|
||||
|
||||
int main() {
|
||||
using clock = std::chrono::steady_clock;
|
||||
|
||||
signal(SIGPIPE, handler);
|
||||
signal(SIGTERM, handler);
|
||||
signal(SIGINT, handler);
|
||||
signal(SIGHUP, handler);
|
||||
|
||||
// Client setting for local testing
|
||||
setenv("EPICS_CA_AUTO_ADDR_LIST", "NO", 1);
|
||||
setenv("EPICS_CA_ADDR_LIST", "127.0.0.255", 1);
|
||||
setenv("EPICS_CA_ADDR_LIST", "127.0.0.1", 1);
|
||||
|
||||
// Create a new instance of the mEpicsCa bus driver and use it to
|
||||
// interact with the spawned IOC.
|
||||
auto int_ca = mEpicsCa<int>("MEPICSCA:TEST:LONGIN", false);
|
||||
auto int_hopr_ca = mEpicsCa<int>("MEPICSCA:TEST:LONGIN.HOPR", false);
|
||||
auto double_ca = mEpicsCa<double>("MEPICSCA:TEST:AI", false);
|
||||
auto string_ca = mEpicsCa<std::string>("MEPICSCA:TEST:STRINGIN", false);
|
||||
auto waveform_ca = mEpicsCa<std::string>("MEPICSCA:TEST:WAVEFORM", false);
|
||||
auto bo_ca = mEpicsCa<uint16_t>("MEPICSCA:TEST:BO", false);
|
||||
|
||||
// Ending s indicates subscription mechanism is active
|
||||
auto int_cas = mEpicsCa<int>("MEPICSCA:TEST:LONGIN", true);
|
||||
|
||||
// Generic status variable which is reused for the error codes throughout
|
||||
// the tests.
|
||||
int status = CM_SUCCESS;
|
||||
|
||||
{
|
||||
// =====================================================================
|
||||
// Tests before IOC is running
|
||||
// =====================================================================
|
||||
|
||||
// Drivers are not connected
|
||||
ASSERT((!int_ca.connected()));
|
||||
|
||||
// Cached values are invalid
|
||||
ASSERT((!int_cas.cached().has_value()));
|
||||
|
||||
// Attempting to write or read a value results in an error. stdout is
|
||||
// suppressed for the duration of this test
|
||||
|
||||
int saved_stdout = dup(STDOUT_FILENO);
|
||||
int devnull = open("/dev/null", O_WRONLY);
|
||||
dup2(devnull, STDOUT_FILENO);
|
||||
close(devnull);
|
||||
|
||||
int int_val = 0;
|
||||
status = int_ca.get(&int_val);
|
||||
EQUAL(status, FE_ERR_DRIVER);
|
||||
|
||||
double new_double_val = 4.2;
|
||||
status = double_ca.put(&new_double_val);
|
||||
EQUAL(status, FE_ERR_DRIVER);
|
||||
|
||||
// Restore stdout
|
||||
dup2(saved_stdout, STDOUT_FILENO);
|
||||
close(saved_stdout);
|
||||
}
|
||||
|
||||
// Scope everything so the IOC gets torn down by the IocProcess destructor
|
||||
// before reporting that the tests were successfull.
|
||||
{
|
||||
using clock = std::chrono::steady_clock;
|
||||
|
||||
// Start the test IOC
|
||||
std::string path = __FILE__;
|
||||
@@ -92,11 +156,6 @@ int main() {
|
||||
std::string ioc_exe = dir + "/ioc/startioc";
|
||||
IocProcess proc = IocProcess(ioc_exe);
|
||||
|
||||
// Create a new instance of the mEpicsCa bus driver and use it to
|
||||
// interact with the spawned IOC.
|
||||
mEpicsCa<int> int_ca = mEpicsCa<int>("TEST:INT");
|
||||
mEpicsCa<double> double_ca = mEpicsCa<double>("TEST:DOUBLE");
|
||||
|
||||
// Wait for the IOC to become available
|
||||
double timeout = 20.0;
|
||||
|
||||
@@ -111,46 +170,260 @@ int main() {
|
||||
auto now = clock::now();
|
||||
double elapsed = std::chrono::duration<double>(now - start).count();
|
||||
if (elapsed > timeout) {
|
||||
printf(
|
||||
"Could not connect to IOC in %.3lf seconds. The IOC likely "
|
||||
"failed to start.\n",
|
||||
timeout);
|
||||
printf("Could not connect to IOC in %.3lf seconds. The IOC "
|
||||
"likely "
|
||||
"failed to start.\n",
|
||||
timeout);
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
int status = ECA_NORMAL;
|
||||
// =====================================================================
|
||||
// No subscription
|
||||
// =====================================================================
|
||||
|
||||
// Read an integer from a record
|
||||
int int_val = 0;
|
||||
status = int_ca.get(&int_val);
|
||||
EQUAL(status, ECA_NORMAL);
|
||||
EQUAL(int_val, 42);
|
||||
{
|
||||
// -----------------------------------------------------------------
|
||||
// Test get
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
// Read a double from a record
|
||||
double double_val = 0.0;
|
||||
status = double_ca.get(&double_val);
|
||||
EQUAL(status, ECA_NORMAL);
|
||||
EQUAL(double_val, 84.0);
|
||||
// Read an integer from a record
|
||||
int int_val = 0;
|
||||
status = int_ca.get(&int_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(int_val, 42);
|
||||
|
||||
// Write an integer to a record and read it to check
|
||||
int new_int_val = 21;
|
||||
status = int_ca.put(&new_int_val);
|
||||
EQUAL(status, ECA_NORMAL);
|
||||
status = int_ca.get(&int_val);
|
||||
EQUAL(status, ECA_NORMAL);
|
||||
EQUAL(int_val, new_int_val);
|
||||
// Read from a different field
|
||||
status = int_hopr_ca.get(&int_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(int_val, 100);
|
||||
|
||||
// Write a double to a record and read it to check
|
||||
double new_double_val = 4.2;
|
||||
status = double_ca.put(&new_double_val);
|
||||
EQUAL(status, ECA_NORMAL);
|
||||
status = double_ca.get(&double_val);
|
||||
EQUAL(status, ECA_NORMAL);
|
||||
EQUAL(double_val, new_double_val);
|
||||
// Read a double from a record
|
||||
double double_val = 0.0;
|
||||
status = double_ca.get(&double_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(double_val, 84.0);
|
||||
|
||||
// Read a string from a stringin record using a string
|
||||
std::string string_val = "";
|
||||
status = string_ca.get(&string_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(string_val, "MyTestString");
|
||||
|
||||
// Read a string from a stringin record using a char array
|
||||
char char_arr_val[40] = {0};
|
||||
status = string_ca.get(char_arr_val, sizeof(char_arr_val));
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(std::string(char_arr_val), "MyTestString");
|
||||
|
||||
// Read a string from a waveform record
|
||||
status = waveform_ca.get(&string_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(string_val, "ABC");
|
||||
|
||||
// Read an enum as a value and as a string
|
||||
uint16_t short_uint_val = 0;
|
||||
status = bo_ca.get(&short_uint_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(short_uint_val, 1);
|
||||
status = bo_ca.get(&string_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(string_val, "ON");
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Test put
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
// Write an integer to a record and read it to check
|
||||
int new_int_val = 21;
|
||||
status = int_ca.put(&new_int_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
status = int_ca.get(&int_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(int_val, new_int_val);
|
||||
|
||||
// Write to the limit field of the longin record
|
||||
new_int_val = 40;
|
||||
status = int_hopr_ca.put(&new_int_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
int_val = 0; // Overwrite with other value
|
||||
status = int_hopr_ca.get(&int_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(int_val, new_int_val);
|
||||
|
||||
// Write a double to a record and read it to check
|
||||
double new_double_val = 4.2;
|
||||
status = double_ca.put(&new_double_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
double_val = 0; // Overwrite with other value
|
||||
status = double_ca.get(&double_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(double_val, new_double_val);
|
||||
|
||||
// Write a string to the stringin record using a string
|
||||
std::string new_string_val = "NewStringValue";
|
||||
status = string_ca.put(&new_string_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
string_val = ""; // Overwrite with other value
|
||||
status = string_ca.get(&string_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(string_val, new_string_val);
|
||||
|
||||
// Write a string to the stringin record using a char array
|
||||
char new_char_arr_val[40] = "NewCharArrValue";
|
||||
status = string_ca.put(new_char_arr_val,
|
||||
(u_long)strlen(new_char_arr_val));
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
char_arr_val[0] = 0; // Overwrite with other value
|
||||
status = string_ca.get(char_arr_val, (u_long)strlen(char_arr_val));
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(std::string(char_arr_val), std::string(new_char_arr_val));
|
||||
|
||||
// Write to an enum with an integer value
|
||||
short_uint_val = 0;
|
||||
status = bo_ca.put(&short_uint_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
short_uint_val = 2; // Overwrite with other value
|
||||
status = bo_ca.get(&short_uint_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(short_uint_val, 0);
|
||||
status = bo_ca.get(&string_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(string_val, "OFF");
|
||||
|
||||
// Write to an enum with a string
|
||||
std::string new_enum_string = "ON";
|
||||
status = bo_ca.put(&new_enum_string);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
status = bo_ca.get(&short_uint_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(short_uint_val, 1);
|
||||
status = bo_ca.get(&string_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(string_val, "ON");
|
||||
|
||||
// Attempt to write an invalid string. stdout is suppressed for the
|
||||
// duration of this test
|
||||
|
||||
int saved_stdout = dup(STDOUT_FILENO);
|
||||
int devnull = open("/dev/null", O_WRONLY);
|
||||
dup2(devnull, STDOUT_FILENO);
|
||||
close(devnull);
|
||||
|
||||
new_enum_string = "INVALID";
|
||||
status = bo_ca.put(&new_enum_string);
|
||||
EQUAL(status, FE_ERR_DRIVER); // Attempted to write invalid variant!
|
||||
|
||||
// Assert that nothing has changed in the record
|
||||
status = bo_ca.get(&short_uint_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(short_uint_val, 1);
|
||||
status = bo_ca.get(&string_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(string_val, "ON");
|
||||
|
||||
// Restore stdout
|
||||
dup2(saved_stdout, STDOUT_FILENO);
|
||||
close(saved_stdout);
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// With subscription
|
||||
// =====================================================================
|
||||
|
||||
{
|
||||
int int_val = 0;
|
||||
int new_int_val = 13;
|
||||
|
||||
// Initial status of cached
|
||||
ASSERT(int_cas.cached().has_value());
|
||||
status = int_cas.get(&int_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
EQUAL(int_val, 21); // Value which was put in the record by int_ca
|
||||
|
||||
status = int_cas.put(&new_int_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
|
||||
// Wait a bit to allow the subscription thread to update
|
||||
double timeout = 5.0;
|
||||
|
||||
auto start = clock::now();
|
||||
while (true) {
|
||||
// Assert that the cached value has changed
|
||||
status = int_cas.get(&int_val);
|
||||
EQUAL(status, CM_SUCCESS);
|
||||
|
||||
if (int_val == new_int_val)
|
||||
break;
|
||||
|
||||
ca_pend_event(0.1);
|
||||
|
||||
auto now = clock::now();
|
||||
double elapsed =
|
||||
std::chrono::duration<double>(now - start).count();
|
||||
if (elapsed > timeout) {
|
||||
printf("Failed to update int_cas value in %lf seconds.\n",
|
||||
timeout);
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
printf("\nTest was successful\n");
|
||||
// Wait for the IOC to go offline
|
||||
double timeout = 20.0;
|
||||
|
||||
auto start = clock::now();
|
||||
while (true) {
|
||||
if (!int_ca.connected()) {
|
||||
break;
|
||||
}
|
||||
|
||||
ca_pend_event(0.1);
|
||||
|
||||
auto now = clock::now();
|
||||
double elapsed = std::chrono::duration<double>(now - start).count();
|
||||
if (elapsed > timeout) {
|
||||
printf("Could not connect to IOC in %.3lf seconds. The IOC "
|
||||
"likely "
|
||||
"failed to start.\n",
|
||||
timeout);
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
{
|
||||
// =====================================================================
|
||||
// Tests after IOC has stopped
|
||||
// =====================================================================
|
||||
|
||||
// Drivers are not connected
|
||||
ASSERT((!int_ca.connected()));
|
||||
|
||||
// Cached values are invalid
|
||||
ASSERT((!int_cas.cached().has_value()));
|
||||
|
||||
// Attempting to write or read a value results in an error. stdout
|
||||
// is suppressed for the duration of this test
|
||||
|
||||
int saved_stdout = dup(STDOUT_FILENO);
|
||||
int devnull = open("/dev/null", O_WRONLY);
|
||||
dup2(devnull, STDOUT_FILENO);
|
||||
close(devnull);
|
||||
|
||||
int int_val = 0;
|
||||
status = int_ca.get(&int_val);
|
||||
EQUAL(status, FE_ERR_DRIVER);
|
||||
|
||||
double new_double_val = 4.2;
|
||||
status = double_ca.put(&new_double_val);
|
||||
EQUAL(status, FE_ERR_DRIVER);
|
||||
|
||||
// Restore stdout
|
||||
dup2(saved_stdout, STDOUT_FILENO);
|
||||
close(saved_stdout);
|
||||
}
|
||||
|
||||
printf("Test was successful.\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user