diff --git a/test/SIM_test_varserv/RUN_test/realtime.py b/test/SIM_test_varserv/RUN_test/realtime.py deleted file mode 100644 index f140a4e9..00000000 --- a/test/SIM_test_varserv/RUN_test/realtime.py +++ /dev/null @@ -1,20 +0,0 @@ -import trick - -def main(): - trick.var_server_set_port(50000) - - trick.set_var_server_info_msg_on() - #trick.sim_control_panel_set_enabled(True) - - trick.real_time_enable() - trick.itimer_enable() - - #trick.add_read(1.1, """vsx.vst.vs_read()""") - #trick.add_read(2.1, """vsx.vst.vs_read()""") - - trick.exec_set_terminate_time(3000.0) - - #print trick.var_add("vsx.vst.b") - -if __name__ == "__main__": - main() diff --git a/test/SIM_test_varserv/RUN_test/unit_test.py b/test/SIM_test_varserv/RUN_test/unit_test.py index 1f79a15d..4872b38a 100644 --- a/test/SIM_test_varserv/RUN_test/unit_test.py +++ b/test/SIM_test_varserv/RUN_test/unit_test.py @@ -7,16 +7,18 @@ def main(): trick.var_server_set_port(40000) trick.var_ascii() + trick.real_time_enable() + trick.exec_set_software_frame(0.01) + # trick.set_var_server_info_msg_on() - #trick.set_var_server_info_msg_on() + trick.exec_set_terminate_time(100.0) - trick_utest.unit_tests.enable() ; - trick_utest.unit_tests.set_file_name( os.getenv("TRICK_HOME") + "/trick_test/SIM_test_varserv.xml" ) - trick_utest.unit_tests.set_test_name( "VariableServerTest" ) + varServerPort = trick.var_server_get_port() + test_output = ( os.getenv("TRICK_HOME") + "/trick_test/SIM_test_varserv.xml" ) + command = 'os.system("./models/test_client/test_client ' + str(varServerPort) + ' --gtest_output=xml:' + test_output + ' &")' - TRICK_EXPECT_EQ(trick.var_server_get_port(), 40000, "VariableServerTest", "SetPortNumber") - - trick.exec_set_terminate_time(3000.0) + # Start the test client after everything has been initialized (hopefully) + trick.add_read(1.0, command) if __name__ == "__main__": main() diff --git a/test/SIM_test_varserv/S_define b/test/SIM_test_varserv/S_define index 7fed83f8..79e25327 100644 --- a/test/SIM_test_varserv/S_define +++ b/test/SIM_test_varserv/S_define @@ -3,11 +3,6 @@ PURPOSE: ( S_define ) LIBRARY DEPENDENCIES: ( (varserv/src/VS.cpp) - (varserv/src/VS_init.cpp) - (varserv/src/VS_default_data.cpp) - (varserv/src/VS_commands.cpp) - (varserv/src/VS_tests.cpp) - (varserv/src/VS_shutdown.cpp) ) *************************************************************/ @@ -22,16 +17,9 @@ class testSimObject : public Trick::SimObject { testSimObject() { ("default_data") vst.default_vars(); ("initialization") vst.init(); - ("initialization") vst.testAddRemove(); - ("initialization") vst.testUnits(); - ("initialization") vst.testExists(); - ("initialization") vst.testPause(); - ("initialization") vst.testSendOnce(); ("shutdown") vst.shutdown(); } }; // Instantiations testSimObject vsx; - - diff --git a/test/SIM_test_varserv/S_overrides.mk b/test/SIM_test_varserv/S_overrides.mk index 27536a3b..7da346c8 100644 --- a/test/SIM_test_varserv/S_overrides.mk +++ b/test/SIM_test_varserv/S_overrides.mk @@ -2,3 +2,13 @@ TRICK_CFLAGS += -I./models TRICK_CXXFLAGS += -I./models +all: test_client +clean: clean_test_client + +TEST_CLIENT_LIBS += -L${GTEST_HOME}/lib64 -L${GTEST_HOME}/lib -lgtest -lgtest_main -lpthread + +test_client: models/test_client/test_client.cpp + cd models/test_client; $(TRICK_CXX) test_client.cpp -o test_client $(TEST_CLIENT_LIBS) -std=c++11 + +clean_test_client: + rm -f models/test_client/test_client \ No newline at end of file diff --git a/test/SIM_test_varserv/models/test_client/.gitignore b/test/SIM_test_varserv/models/test_client/.gitignore new file mode 100644 index 00000000..837a5b2a --- /dev/null +++ b/test/SIM_test_varserv/models/test_client/.gitignore @@ -0,0 +1 @@ +test_client \ No newline at end of file diff --git a/test/SIM_test_varserv/models/test_client/test_client.cpp b/test/SIM_test_varserv/models/test_client/test_client.cpp new file mode 100644 index 00000000..f859a3fa --- /dev/null +++ b/test/SIM_test_varserv/models/test_client/test_client.cpp @@ -0,0 +1,561 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define SOCKET_BUF_SIZE 20480 + +class Socket { + + public: + + int max_retries = 10; + + Socket() : _initialized(false) {} + int init(std::string hostname, int port) { + _hostname = hostname; + _port = port; + int tries = 0; + + while ((_socket_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0 && tries < max_retries) tries++; + + if (_socket_fd < 0) { + std::cout << "Socket connection failed" << std::endl; + return -1; + } + + struct sockaddr_in serv_addr; + serv_addr.sin_family = AF_INET; + serv_addr.sin_port = htons(port); // convert to weird network byte format + + if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr)<=0) { + std::cout << "Invalid address/ Address not supported" << std::endl; + return -1; + } + + tries = 0; + int connection_status; + + while ((connection_status = connect(_socket_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) < 0 && tries < max_retries) tries++; + + if (connection_status < 0) { + std::cout << "Connection failed" << std::endl; + return -1; + } + + _initialized = true; + + return 0; + } + + int send (std::string message) { + // weird syntax I've never used before - since the send syscall that i'm trying to use is overloaded in this class, + // I have to append :: to the front of it so that the compiler knows to look in the global namespace + int success = ::send(_socket_fd, message.c_str(), message.size(), 0); + if (success < message.size()) { + std::cout << "Failed to send message" << std::endl; + } + return success; + } + + int operator<< (std::string message) { + return send(message); + } + + std::string receive () { + char buffer[SOCKET_BUF_SIZE]; + int numBytes = recv(_socket_fd, buffer, SOCKET_BUF_SIZE, 0); + if (numBytes < 0) { + } else if (numBytes < SOCKET_BUF_SIZE) { + buffer[numBytes] = '\0'; + } + + return std::string(buffer); + } + + void operator>> (std::string& ret) { + ret = receive(); + } + + bool check_for_message_availible(long timeout_sec = 2) { + fd_set read_fd_set; + struct timeval timeout = { .tv_sec = timeout_sec, .tv_usec = 0 }; + FD_ZERO(&read_fd_set); + FD_SET(_socket_fd, &read_fd_set); + + // I have one question for the designers of the interface for this syscall: why + select(_socket_fd+1, &read_fd_set, NULL, NULL, &timeout); + + return FD_ISSET(_socket_fd, &read_fd_set); + } + + void clear_buffered_data() { + // Poll the socket + if (check_for_message_availible(0)) { + // Receive into the void if there is a message + receive(); + } + // otherwise no worries + } + + void close() { + ::close(_socket_fd); + } + + private: + int _port; + std::string _hostname; + int _socket_fd; + bool _initialized; + +}; + +int strcmp_IgnoringWhiteSpace(std::string s1_str, std::string s2_str) { + const char * s1 = s1_str.c_str(); + const char * s2 = s2_str.c_str(); + + int i1 = 0; + int i2 = 0; + + while (1) { + while ( !isgraph( s1[i1] ) && s1[i1] != '\0') { i1++; } + while ( !isgraph( s2[i2] ) && s2[i2] != '\0') { i2++; } + if ( s1[i1] == '\0' && s2[i2] == '\0') { return 0; } + if ( s1[i1] != s2[i2]) { + if (s1[i1] < s2[i2]) { + return -1; + } else { + return 1; + } + } + i1++; i2++; + } +} + +class VariableServerTest : public ::testing::Test { + protected: + VariableServerTest() { + socket_status = socket.init("localhost", 40000); + + if (socket_status == 0) { + std::stringstream request; + request << "trick.var_set_client_tag(\"VSTest"; + request << numSession++; + request << "\") \n"; + + socket << request.str(); + } + } + ~VariableServerTest() { + socket << "trick.var_exit()\n"; + socket.close(); + } + + Socket socket; + int socket_status; + + static int numSession; +}; + +int VariableServerTest::numSession = 0; + +TEST_F (VariableServerTest, Strings) { + if (socket_status != 0) { + FAIL(); + } + + std::string reply; + socket << "trick.var_send_once(\"vsx.vst.o\")\n"; + socket >> reply; + std::string expected("5\tYou will rejoice to hear that no disaster has accompanied the commencement of an enterprise which you have regarded with such evil forebodings. I arrived here yesterday, and my first task is to assure my dear sister of my welfare and increasing confidence in the success of my undertaking."); + + EXPECT_EQ(strcmp_IgnoringWhiteSpace(reply, expected), 0); + + + expected = std::string("5\tI am already far north of London, and as I walk in the streets of Petersburgh, I feel a cold northern breeze play upon my cheeks, which braces my nerves and fills me with delight. Do you understand this feeling?"); + socket << "trick.var_send_once(\"vsx.vst.p\")\n"; + + socket >> reply; + + EXPECT_EQ(strcmp_IgnoringWhiteSpace(reply, expected), 0); + + // TODO: Does wchar actually work? + // expected = std::string("5\tThis breeze, which has travelled from the regions towards which I am advancing, gives me a foretaste of those icy climes. Inspirited by this wind of promise, my daydreams become more fervent and vivid."); + // socket << "trick.var_send_once(\"vsx.vst.q\")\n"; + + // socket >> reply; + + // std::cout << "\tExpected: " << expected << "\n\tActual: " << reply << std::endl; + + // EXPECT_EQ(strcmp_IgnoringWhiteSpace(reply, expected), 0); +} + +TEST_F (VariableServerTest, AddRemove) { + if (socket_status != 0) { + FAIL(); + } + + std::string reply; + std::string expected; + + socket << "trick.var_add(\"vsx.vst.c\")\n"; + socket >> reply; + expected = std::string("0 -1234"); + + EXPECT_EQ(strcmp_IgnoringWhiteSpace(reply, expected), 0); + + socket >> reply; + + EXPECT_EQ(strcmp_IgnoringWhiteSpace(reply, expected), 0); + + socket << "trick.var_add(\"vsx.vst.m\")\n"; + socket >> reply; + expected = std::string("0 -1234 1"); + + EXPECT_EQ(strcmp_IgnoringWhiteSpace(reply, expected), 0); + + socket << "trick.var_remove(\"vsx.vst.m\")\n"; + socket >> reply; + expected = std::string("0 -1234"); + + EXPECT_EQ(strcmp_IgnoringWhiteSpace(reply, expected), 0); +} + +TEST_F (VariableServerTest, BadRefResponse) { + if (socket_status != 0) { + FAIL(); + } + + std::string reply; + std::string expected; + + std::cerr << "The purpose of this test is to cause an error. Error messages are expected." << std::endl; + + socket << "trick.var_send_once(\"vsx.vst.no_such_variable\")\n"; + socket >> reply; + expected = std::string("5 BAD_REF"); + + EXPECT_EQ(strcmp_IgnoringWhiteSpace(reply, expected), 0); +} + + +TEST_F (VariableServerTest, Units) { + if (socket_status != 0) { + FAIL(); + } + + std::string reply; + std::string expected; + + std::cerr << "The purpose of this test is to cause an error. Error messages are expected." << std::endl; + socket << "trick.var_add(\"vsx.vst.c\")\ntrick.var_units(\"vsx.vst.c\",\"g\")\n"; + socket >> reply; + expected = std::string("0 -1234"); + + EXPECT_EQ(strcmp_IgnoringWhiteSpace(reply, expected), 0); + + socket << "trick.var_add(\"vsx.vst.e\",\"m\")\n"; + socket >> reply; + expected = std::string("0 -1234 -123456 {m}"); + + EXPECT_EQ(strcmp_IgnoringWhiteSpace(reply, expected), 0); + + socket << "trick.var_units(\"vsx.vst.e\",\"ft\")\n"; + socket >> reply; + expected = std::string("0 -1234 -405039 {ft}"); + + EXPECT_EQ(strcmp_IgnoringWhiteSpace(reply, expected), 0); +} + +TEST_F (VariableServerTest, SendOnce) { + if (socket_status != 0) { + FAIL(); + } + + std::string reply; + std::string expected; + + socket << "trick.var_send_once(\"vsx.vst.e\")\n"; + socket >> reply; + expected = std::string("5 -123456"); + + EXPECT_EQ(strcmp_IgnoringWhiteSpace(reply, expected), 0); + + // Check that nothing is sent cyclically + EXPECT_EQ(socket.check_for_message_availible(), false); + + socket << "trick.var_send_once(\"vsx.vst.n[0], vsx.vst.n[1], vsx.vst.n[2],\", 3)\n"; + socket >> reply; + expected = std::string("5 0 1 2"); + + EXPECT_EQ(strcmp_IgnoringWhiteSpace(reply, expected), 0); + + std::cerr << "The purpose of this test is to cause an error. Error messages are expected." << std::endl; + socket << "trick.var_send_once(\"vsx.vst.n[0], vsx.vst.n[1], vsx.vst.n[2],\", 4)\n"; + + // Wrong number of variables, should not send a response but should see an error message from the Trick side + EXPECT_EQ(socket.check_for_message_availible(), false); +} + +TEST_F (VariableServerTest, Exists) { + if (socket_status != 0) { + FAIL(); + } + + std::string reply; + std::string expected; + + socket << "trick.var_exists(\"vsx.vst.e\")\n"; + socket >> reply; + expected = std::string("1 1"); + + EXPECT_EQ(strcmp_IgnoringWhiteSpace(reply, expected), 0); + + + socket << "trick.var_exists(\"vsx.vst.z\")\n"; + socket >> reply; + expected = std::string("1 0"); + + EXPECT_EQ(strcmp_IgnoringWhiteSpace(reply, expected), 0); + +} + + +TEST_F (VariableServerTest, Cycle) { + if (socket_status != 0) { + FAIL(); + } + + double cycle = 1.0; + double tolerance = 0.01; + int num_cycles = 5; + + // Challenge: no loops allowed + // I've been reading about lamdbas and when you have a hammer........ + + // Test: compare the differences in the returned sim time, make sure the difference + // between them are equal to what var_cycle has been set to within some tolerance + // Problem: setting a tolerance empirically is subject to fluctuactions in the environment, + // which could cause the test to fail even though everthing is functioning correctly + // For example, the original version of tolerances here passed in all the CI pipelines + // except Mac, which for some reason was returning much more variation in cycle time + + // Instead, calculate the average cycle time for a few cycles, and make sure this + // value is closer to the current set cycle time than the previous set cycle time. + // This gives us less information before, we are basically only testing that + // var_cycle is actually changing the cycle time, instead of testing that the + // cycle time is being closely adhered to, but it shouldn't fail the pipeline unnecessarily + // And testing something is better than nothing + + + auto parse_message_for_sim_time = [](const std::string& message) { + // For this case the message will be + // 0\t