429 lines
15 KiB
C++
429 lines
15 KiB
C++
#include "cadef.h"
|
|
#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>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
|
|
#define ASSERT(cond) \
|
|
do { \
|
|
if (!(cond)) { \
|
|
std::cerr << "Test failed: " #cond << std::endl; \
|
|
return 1; \
|
|
} \
|
|
} while (0)
|
|
|
|
#define EQUAL(left, right) \
|
|
do { \
|
|
if (!(left == right)) { \
|
|
std::cerr << "\nTest failed at line " << __LINE__ << ": " \
|
|
<< (left) << " (" << #left << ") != " << (right) << " (" \
|
|
<< #right << *")" << std::endl; \
|
|
return 1; \
|
|
} \
|
|
} while (0)
|
|
|
|
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
|
|
execl(path.c_str(), path.c_str(), (char *)NULL);
|
|
perror("execl failed");
|
|
_exit(1);
|
|
}
|
|
|
|
if (_pid < 0) {
|
|
throw std::runtime_error("fork failed");
|
|
}
|
|
}
|
|
|
|
~IocProcess() {
|
|
if (_pid <= 0)
|
|
return;
|
|
|
|
kill(_pid, SIGTERM);
|
|
|
|
// Give the IOC 50 * 100 * 1000 = 5 seconds time for graceful
|
|
// termination.
|
|
for (int i = 0; i < 50; i++) {
|
|
if (waitpid(_pid, nullptr, WNOHANG) == _pid) {
|
|
return;
|
|
}
|
|
// Wait for 0.1 seconds between iterations
|
|
usleep(100 * 1000);
|
|
}
|
|
|
|
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.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.
|
|
{
|
|
|
|
// Start the test IOC
|
|
std::string path = __FILE__;
|
|
std::string dir = path.substr(0, path.find_last_of("/\\"));
|
|
std::string ioc_exe = dir + "/ioc/startioc";
|
|
IocProcess proc = IocProcess(ioc_exe);
|
|
|
|
// Wait for the IOC to become available
|
|
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;
|
|
}
|
|
};
|
|
|
|
// =====================================================================
|
|
// No subscription
|
|
// =====================================================================
|
|
|
|
{
|
|
// -----------------------------------------------------------------
|
|
// Test get
|
|
// -----------------------------------------------------------------
|
|
|
|
// Read an integer from a record
|
|
int int_val = 0;
|
|
status = int_ca.get(&int_val);
|
|
EQUAL(status, CM_SUCCESS);
|
|
EQUAL(int_val, 42);
|
|
|
|
// Read from a different field
|
|
status = int_hopr_ca.get(&int_val);
|
|
EQUAL(status, CM_SUCCESS);
|
|
EQUAL(int_val, 100);
|
|
|
|
// 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;
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
} |