From 5f6a5de0704fa07629785c0f94fd58220964cf65 Mon Sep 17 00:00:00 2001 From: Jacqueline Deans Date: Fri, 24 Feb 2023 14:44:55 -0600 Subject: [PATCH] Expand SIM_test_varserver (#1459) * Add tests for alternate ways to open VS port * Don't connect to varserv when quiet=true in trickops * Add print to try to help debug hanging trickops test * Handle multicast connect failures gracefully * Multicast is disabled by default on mac * Forgot an important return value * Take away retries * Fix issue with restart test * Revert trickops debugging changes * Remove debugging accidentally left in [no ci] * whoops * Allow retries * Update trickops.py * sim test adjustments * Add docs [no ci] * wording [no ci] * Cleanup * Remove large messages, test that one in unit tests --- .../Variable-Server.md | 12 + include/trick/var_binary_parser.hh | 28 +- test/.gitignore | 1 + test/SIM_test_varserv/RUN_test/manual_test.py | 31 + test/SIM_test_varserv/RUN_test/unit_test.py | 6 +- test/SIM_test_varserv/S_overrides.mk | 5 +- .../models/test_client/test_client.cpp | 768 ++++++++++++++++-- .../models/varserv/include/VS.hh | 4 +- .../models/varserv/src/VS.cpp | 11 +- .../VariableServerListenThread.cpp | 5 +- .../src/var_binary_parser.cc | 362 +++++---- .../test/TEST_var_binary_parser.cc | 29 + trickops.py | 19 +- 13 files changed, 1041 insertions(+), 240 deletions(-) create mode 100644 test/SIM_test_varserv/RUN_test/manual_test.py diff --git a/docs/documentation/simulation_capabilities/Variable-Server.md b/docs/documentation/simulation_capabilities/Variable-Server.md index 6817765d..6dc5824b 100644 --- a/docs/documentation/simulation_capabilities/Variable-Server.md +++ b/docs/documentation/simulation_capabilities/Variable-Server.md @@ -63,6 +63,16 @@ trick.var_server_get_hostname() trick.var_server_get_port() ``` +Additional TCP or UDP sockets can be opened as well. Additional TCP sockets operate the same way as the original variable server socket. A UDP socket will only host 1 variable server session, and the responses will be sent to the latest address that sends commands to it. + +Note that this is not necessary to allow multiple variable server clients - any number of clients can connect to the original variable server port. + +```python +trick.var_server_create_udp_socket( const char * source_address, unsigned short port ) +trick.var_server_create_tcp_socket( const char * source_address, unsigned short port ) +``` + + ### Commands The variable server accepts commands in the form of strings. The variable server parses @@ -510,6 +520,8 @@ on your network sends it's information to this address and port so there may be messages with variable server information available here. Here is some C code that reads all messages on the variable server channel. +Note that the multicast protocol is disabled by default in MacOS. + ```c #include #include diff --git a/include/trick/var_binary_parser.hh b/include/trick/var_binary_parser.hh index eb96f9b0..723166b5 100644 --- a/include/trick/var_binary_parser.hh +++ b/include/trick/var_binary_parser.hh @@ -1,6 +1,7 @@ #include #include #include +#include #include #include "trick/parameter_types.h" @@ -54,6 +55,22 @@ typedef union Number { double double_val; } Number; +const static std::map type_size_map = {{TRICK_CHARACTER, sizeof(char)}, + {TRICK_UNSIGNED_CHARACTER, sizeof(unsigned char)}, + {TRICK_SHORT, sizeof(short)}, + {TRICK_UNSIGNED_SHORT, sizeof(unsigned short)}, + {TRICK_INTEGER, sizeof(int)}, + {TRICK_UNSIGNED_INTEGER, sizeof(unsigned int)}, + {TRICK_LONG, sizeof(long)}, + {TRICK_UNSIGNED_LONG, sizeof(unsigned long)}, + {TRICK_LONG_LONG, sizeof(long long)}, + {TRICK_UNSIGNED_LONG_LONG, sizeof(unsigned long)}, + {TRICK_FLOAT, sizeof (float)}, + {TRICK_DOUBLE, sizeof (double)}, + {TRICK_BOOLEAN, sizeof (bool)}, + {TRICK_WCHAR, sizeof (wchar_t)} + }; + class Var { public: @@ -65,10 +82,13 @@ class Var { // There won't be a general case template T getValue() const; - + std::vector getRawBytes() const; + + int getArraySize() const; std::string getName() const; TRICK_TYPE getType() const; + private: std::vector value_bytes; Number getInterpreter () const; @@ -81,16 +101,18 @@ class Var { TRICK_TYPE _trick_type; unsigned int _var_size; + unsigned int _arr_length; }; class ParsedBinaryMessage { public: - ParsedBinaryMessage() : _byteswap(false), _nonames(false) {} - ParsedBinaryMessage (bool byteswap, bool nonames) : _byteswap(byteswap), _nonames(nonames) {} + ParsedBinaryMessage() : ParsedBinaryMessage(false, false) {} + ParsedBinaryMessage (bool byteswap, bool nonames) : _message_type(0), _message_size(0), _num_vars(0), _byteswap(byteswap), _nonames(nonames) {} void combine (const ParsedBinaryMessage& message); int parse (const std::vector& bytes); + int parse (char * raw_bytes); int getMessageType() const; unsigned int getMessageSize() const; diff --git a/test/.gitignore b/test/.gitignore index 38e6eddd..1e4698c4 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -25,3 +25,4 @@ trick.zip jitlib build S_sie.json +*.ckpnt \ No newline at end of file diff --git a/test/SIM_test_varserv/RUN_test/manual_test.py b/test/SIM_test_varserv/RUN_test/manual_test.py new file mode 100644 index 00000000..7e8050a7 --- /dev/null +++ b/test/SIM_test_varserv/RUN_test/manual_test.py @@ -0,0 +1,31 @@ +import trick +import socket + +from trick.unit_test import * + +def main(): + + trick.var_server_set_port(4000) + trick.var_ascii() + trick.real_time_enable() + trick.exec_set_software_frame(0.01) + # trick.set_var_server_info_msg_on() + + trick.var_server_create_tcp_socket('localhost', 49000) + trick.var_server_create_udp_socket('', 48000) + # trick.var_server_create_multicast_socket('224.10.10.10','', 47000) + + # trick.exec_set_terminate_time(100000.0) + + 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 + ' &")' + + # Start the test client after everything has been initialized (hopefully) + # trick.add_read(1.0, command) + trick.sim_control_panel_set_enabled(True) + + +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 4872b38a..1f1231e3 100644 --- a/test/SIM_test_varserv/RUN_test/unit_test.py +++ b/test/SIM_test_varserv/RUN_test/unit_test.py @@ -11,7 +11,11 @@ def main(): trick.exec_set_software_frame(0.01) # trick.set_var_server_info_msg_on() - trick.exec_set_terminate_time(100.0) + trick.var_server_create_tcp_socket('localhost', 49000) + trick.var_server_create_udp_socket('', 48000) + # trick.var_server_create_multicast_socket('224.10.10.10','', 47000) + + trick.exec_set_terminate_time(1000.0) varServerPort = trick.var_server_get_port() test_output = ( os.getenv("TRICK_HOME") + "/trick_test/SIM_test_varserv.xml" ) diff --git a/test/SIM_test_varserv/S_overrides.mk b/test/SIM_test_varserv/S_overrides.mk index a2619dbe..36707008 100644 --- a/test/SIM_test_varserv/S_overrides.mk +++ b/test/SIM_test_varserv/S_overrides.mk @@ -8,7 +8,8 @@ clean: clean_test_client TEST_CLIENT_LIBS += -L${GTEST_HOME}/lib64 -L${GTEST_HOME}/lib -lgtest -lgtest_main -lpthread -L${TRICK_LIB_DIR} -ltrick_var_binary_parser test_client: models/test_client/test_client.cpp - cd models/test_client; $(TRICK_CXX) test_client.cpp -o test_client $(TRICK_CXXFLAGS) $(TRICK_SYSTEM_CXXFLAGS) $(TRICK_TEST_FLAGS) -I$(TRICK_HOME)/include $(TEST_CLIENT_LIBS) + cd models/test_client; $(TRICK_CXX) test_client.cpp -o test_client $(TRICK_CXXFLAGS) $(TRICK_SYSTEM_CXXFLAGS) $(TRICK_TEST_FLAGS) -Wno-write-strings -Wno-sign-compare -I$(TRICK_HOME)/include $(TEST_CLIENT_LIBS) clean_test_client: - rm -f models/test_client/test_client \ No newline at end of file + rm -f models/test_client/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 index 37e58504..80de986d 100644 --- a/test/SIM_test_varserv/models/test_client/test_client.cpp +++ b/test/SIM_test_varserv/models/test_client/test_client.cpp @@ -10,14 +10,15 @@ #include #include #include +#include +#include #include #include "trick/var_binary_parser.hh" -#define SOCKET_BUF_SIZE 20480 - #define DOUBLE_TOL 1e-5 +#define SOCKET_BUF_SIZE 204800 // Pretend that gtest was kind enough to provide an EXPECT_FEQ operator with a tolerance #define EXPECT_FEQ(a,b) EXPECT_LE(fabs(a - b), DOUBLE_TOL) @@ -29,12 +30,14 @@ class Socket { int max_retries = 10; Socket() : _initialized(false) {} - int init(std::string hostname, int port) { + int init(std::string hostname, int port, int mode = SOCK_STREAM) { + _multicast_socket = false; + _hostname = hostname; _port = port; int tries = 0; - while ((_socket_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0 && tries < max_retries) tries++; + while ((_socket_fd = socket(AF_INET, mode, 0)) < 0 && tries < max_retries) tries++; if (_socket_fd < 0) { std::cout << "Socket connection failed" << std::endl; @@ -65,12 +68,60 @@ class Socket { return 0; } + #ifndef __APPLE__ + int init_multicast (std::string hostname, int port) { + _multicast_socket = true; + _hostname = hostname; + _port = port; + int tries = 0; + + while ((_socket_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0 && tries < max_retries) tries++; + + if (_socket_fd < 0) { + std::cout << "init_multicast: Socket open failed" << std::endl; + return -1; + } + + int value = 1; + if (setsockopt(_socket_fd, SOL_SOCKET, SO_REUSEADDR, (char *) &value, (socklen_t) sizeof(value)) < 0) { + std::cout << "init_multicast: Socket option failed" << std::endl; + return -1; + } + + struct ip_mreq mreq; + // Use setsockopt() to request that the kernel join a multicast group + mreq.imr_multiaddr.s_addr = inet_addr(_hostname.c_str()); + mreq.imr_interface.s_addr = htonl(INADDR_ANY); + if (setsockopt(_socket_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *) &mreq, (socklen_t) sizeof(mreq)) < 0) { + std::cout << "init_multicast: setsockopt: ip_add_membership failed" << std::endl; + return -1; + } + + + struct sockaddr_in sockin ; + + // Set up destination address + sockin.sin_family = AF_INET; + sockin.sin_addr.s_addr = htonl(INADDR_ANY); + sockin.sin_port = htons(_port); + + if ( bind(_socket_fd, (struct sockaddr *) &sockin, (socklen_t) sizeof(sockin)) < 0 ) { + std::cout << "init_multicast: bind failed" << std::endl; + return -1; + } + + _initialized = true; + return 0; + } + + #endif + 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; + std::cout << "init_multicast: Failed to send message" << std::endl; } return success; } @@ -98,7 +149,7 @@ class Socket { unsigned char buffer[SOCKET_BUF_SIZE]; int numBytes = recv(_socket_fd, buffer, SOCKET_BUF_SIZE, 0); if (numBytes < 0) { - std::cout << "Failed to read from socket" << std::endl; + std::cout << "init_multicast: Failed to read from socket" << std::endl; } std::vector bytes; @@ -139,9 +190,97 @@ class Socket { std::string _hostname; int _socket_fd; bool _initialized; + bool _multicast_socket; }; +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; +}; + +class VariableServerUDPTest : public ::testing::Test { + protected: + VariableServerUDPTest() { + socket_status = socket.init("localhost", 48000, SOCK_DGRAM); + + if (socket_status == 0) { + std::stringstream request; + request << "trick.var_set_client_tag(\"UDP_VSTest"; + request << numSession++; + request << "\") \n"; + + socket << request.str(); + } + } + ~VariableServerUDPTest() { + socket.close(); + } + + Socket socket; + int socket_status; + + static int numSession; +}; + +class VariableServerTestAltListener : public ::testing::Test { + protected: + VariableServerTestAltListener() { + socket_status = socket.init("localhost", 49000); + + if (socket_status == 0) { + std::stringstream request; + request << "trick.var_set_client_tag(\"altListener_VSTest"; + request << numSession++; + request << "\") \n"; + + socket << request.str(); + } + } + ~VariableServerTestAltListener() { + socket << "trick.var_exit()\n"; + socket.close(); + } + + Socket socket; + int socket_status; + + static int numSession; +}; + +int VariableServerTest::numSession = 0; +int VariableServerUDPTest::numSession = 0; +int VariableServerTestAltListener::numSession = 0; + + + +/**********************************************************/ +/* Helpful constants and functions */ +/**********************************************************/ + + +const int MODE_RUN = 5; +const int MODE_FREEZE = 1; + 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(); @@ -164,32 +303,212 @@ int strcmp_IgnoringWhiteSpace(std::string s1_str, std::string s2_str) { } } -class VariableServerTest : public ::testing::Test { - protected: - VariableServerTest() { - socket_status = socket.init("localhost", 40000); +void spin (Socket& socket, int wait_cycles = 5) { + for (int i = 0; i < wait_cycles; i++) + socket.receive(); +} - if (socket_status == 0) { - std::stringstream request; - request << "trick.var_set_client_tag(\"VSTest"; - request << numSession++; - request << "\") \n"; +void wait_for_mode_change (Socket& socket, int expected_mode, int max_wait_iterations = 10) { + int iteration = 0; + std::string mode_reply; + std::string expected_reply = "5\t" + std::to_string(expected_mode) + "\n"; + while (iteration++ < max_wait_iterations) { + socket << "trick.var_send_once(\"trick_sys.sched.mode\")\n"; + socket >> mode_reply; + if (mode_reply == expected_reply) + break; + } +} - socket << request.str(); - } - } - ~VariableServerTest() { - socket << "trick.var_exit()\n"; - socket.close(); - } +void dump_checkpoint (Socket& socket, const std::string& checkpoint_name) { + socket << "trick.var_pause()\ntrick.exec_freeze()\n"; + wait_for_mode_change(socket, MODE_FREEZE); - Socket socket; - int socket_status; - - static int numSession; -}; + std::string checkpoint_cmd = "trick.checkpoint(\"" + checkpoint_name + "\")\n"; + socket << checkpoint_cmd; -int VariableServerTest::numSession = 0; + socket << "trick.exec_run()\n"; + wait_for_mode_change(socket, MODE_RUN); + socket << "trick.var_unpause()\n"; +} + +void load_checkpoint (Socket& socket, const std::string& checkpoint_name) { + socket << "trick.var_pause()\ntrick.exec_freeze()\n"; + wait_for_mode_change(socket, MODE_FREEZE); + + std::string checkpoint_cmd = "trick.load_checkpoint(\"" + checkpoint_name + "\")\n"; + socket << checkpoint_cmd; + sleep(5); + + socket << "trick.exec_run()\n"; + wait_for_mode_change(socket, MODE_RUN); + socket << "trick.var_unpause()\n"; +} + +/************************************/ +/* UDP Tests */ +/************************************/ + + +TEST_F (VariableServerUDPTest, 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); +} + + +TEST_F (VariableServerUDPTest, 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); + + socket << "trick.var_add(\"vsx.vst.n\")\n"; + socket >> reply; + expected = std::string("0 -1234 0,1,2,3,4"); + EXPECT_EQ(strcmp_IgnoringWhiteSpace(reply, expected), 0); + + socket << "trick.var_exit()\n"; +} + +/*********************************************/ +/* Alt Listener Tests */ +/*********************************************/ + +TEST_F (VariableServerTestAltListener, 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); +} + +TEST_F (VariableServerTestAltListener, 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); + + socket << "trick.var_add(\"vsx.vst.n\")\n"; + socket >> reply; + expected = std::string("0 -1234 0,1,2,3,4"); + EXPECT_EQ(strcmp_IgnoringWhiteSpace(reply, expected), 0); +} + + +TEST_F (VariableServerTestAltListener, RestartAndSet) { + 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\t-1234\n"); + + EXPECT_EQ(reply, expected); + + dump_checkpoint(socket, "reload_file.ckpnt"); + + socket << "trick.var_add(\"vsx.vst.e\",\"m\")\n"; + socket >> reply; + expected = std::string("0\t-1234\t-123456 {m}\n"); + + socket << "trick.var_set(\"vsx.vst.c\", 5)\n"; + socket >> reply; + expected = std::string("0\t5\t-123456 {m}\n"); + + EXPECT_EQ(reply, expected); + + load_checkpoint(socket, "RUN_test/reload_file.ckpnt"); + + socket >> reply; + expected = std::string("0\t-1234\t-123456\n"); + + EXPECT_EQ(reply, expected); +} + +/*********************************************/ +/* Normal case tests */ +/*********************************************/ TEST_F (VariableServerTest, Strings) { if (socket_status != 0) { @@ -222,6 +541,41 @@ TEST_F (VariableServerTest, Strings) { // EXPECT_EQ(strcmp_IgnoringWhiteSpace(reply, expected), 0); } +TEST_F (VariableServerTest, NoExtraTab) { + 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\t-1234\n"); + + EXPECT_STREQ(reply.c_str(), expected.c_str()); + + socket >> reply; + + EXPECT_STREQ(reply.c_str(), expected.c_str()); + + socket << "trick.var_add(\"vsx.vst.m\")\n"; + socket >> reply; + expected = std::string("0\t-1234\t1\n"); + + EXPECT_STREQ(reply.c_str(), expected.c_str()); + + socket << "trick.var_remove(\"vsx.vst.m\")\n"; + socket >> reply; + expected = std::string("0\t-1234\n"); + + socket << "trick.var_add(\"vsx.vst.n\")\n"; + socket >> reply; + expected = std::string("0\t-1234\t0,1,2,3,4\n"); + + EXPECT_STREQ(reply.c_str(), expected.c_str()); +} + TEST_F (VariableServerTest, AddRemove) { if (socket_status != 0) { FAIL(); @@ -256,7 +610,6 @@ TEST_F (VariableServerTest, AddRemove) { socket >> reply; expected = std::string("0 -1234 0,1,2,3,4"); EXPECT_EQ(strcmp_IgnoringWhiteSpace(reply, expected), 0); - } TEST_F (VariableServerTest, BadRefResponse) { @@ -356,8 +709,94 @@ TEST_F (VariableServerTest, Exists) { EXPECT_EQ(strcmp_IgnoringWhiteSpace(reply, expected), 0); + socket << "trick.var_binary()\n"; + + std::vector actual_bytes; + std::vector expected_bytes; + socket << "trick.var_exists(\"vsx.vst.e\")\n"; + actual_bytes = socket.receive_bytes(); + expected_bytes = {0x01, 0x00, 0x00, 0x00, 0x01}; + + EXPECT_EQ(expected_bytes, actual_bytes); + + + socket << "trick.var_exists(\"vsx.vst.z\")\n"; + actual_bytes = socket.receive_bytes(); + expected_bytes = {0x01, 0x00, 0x00, 0x00, 0x00}; + + EXPECT_EQ(expected_bytes, actual_bytes); + } +TEST_F (VariableServerTest, ListSize) { + if (socket_status != 0) { + FAIL(); + } + + std::string reply; + std::string expected; + + socket << "trick.var_add(\"vsx.vst.a\")\ntrick.var_add(\"vsx.vst.b\")\ntrick.var_add(\"vsx.vst.c\")\ntrick.var_add(\"vsx.vst.d\")\ntrick.var_pause()\n"; + socket << "trick.var_send_list_size()\n"; + + socket >> reply; + expected = "3 4"; + + EXPECT_EQ(strcmp_IgnoringWhiteSpace(reply, expected), 0) << "Reply: " << reply << "\tExpected: " << expected; + + // Adding a variable to the list more than once is allowed + socket << "trick.var_add(\"vsx.vst.a\")\n"; + socket << "trick.var_send_list_size()\n"; + + socket >> reply; + expected = "3 5"; + + EXPECT_EQ(strcmp_IgnoringWhiteSpace(reply, expected), 0) << "Reply: " << reply << "\tExpected: " << expected; + + socket << "trick.var_add(\"vsx.vst.e\")\ntrick.var_binary()\ntrick.var_send_list_size()\n"; + + std::vector actual_bytes; + std::vector expected_bytes; + actual_bytes = socket.receive_bytes(); + expected_bytes = {0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00}; + + EXPECT_EQ(expected_bytes, actual_bytes); +} + + +TEST_F (VariableServerTest, RestartAndSet) { + 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\t-1234\n"); + + EXPECT_EQ(reply, expected); + + dump_checkpoint(socket, "reload_file.ckpnt"); + + socket << "trick.var_add(\"vsx.vst.e\",\"m\")\n"; + socket >> reply; + expected = std::string("0\t-1234\t-123456 {m}\n"); + + socket << "trick.var_set(\"vsx.vst.c\", 5)\n"; + socket >> reply; + expected = std::string("0\t5\t-123456 {m}\n"); + + EXPECT_EQ(reply, expected); + + load_checkpoint(socket, "RUN_test/reload_file.ckpnt"); + + socket >> reply; + expected = std::string("0\t-1234\t-123456\n"); + + EXPECT_EQ(reply, expected); +} TEST_F (VariableServerTest, Cycle) { if (socket_status != 0) { @@ -365,7 +804,6 @@ TEST_F (VariableServerTest, Cycle) { } double cycle = 1.0; - double tolerance = 1e-5; int num_cycles = 5; // Challenge: no loops allowed @@ -408,7 +846,7 @@ TEST_F (VariableServerTest, Cycle) { std::string command = "trick.var_set_copy_mode(1)\ntrick.var_set_write_mode(1)\ntrick.var_add(\"time\")\n"; socket << command; - + cycle = 0.1; num_cycles = 5; run_cycle_test(); @@ -451,6 +889,84 @@ TEST_F (VariableServerTest, Pause) { EXPECT_EQ(strcmp_IgnoringWhiteSpace(reply, expected), 0); } +#ifndef __APPLE__ + +TEST_F (VariableServerTest, Multicast) { + if (socket_status != 0) { + FAIL(); + } + + socket << "trick.var_server_set_user_tag(\"VSTestServer\")\n"; + + Socket multicast_socket; + if (multicast_socket.init_multicast("224.3.14.15", 9265) != 0) { + FAIL() << "Multicast Socket failed to initialize."; + } + + int max_multicast_tries = 100; + int tries = 0; + bool found = false; + + char expected_hostname[80]; + gethostname(expected_hostname, 80); + int expected_port = 40000; + + // get expected username + struct passwd *passp = getpwuid(getuid()) ; + char * expected_username; + if ( passp == NULL ) { + expected_username = strdup("unknown") ; + } else { + expected_username = strdup(passp->pw_name) ; + } + + // Don't care about PID, just check that it's > 0 + char * expected_sim_dir = "trick/test/SIM_test_varserv"; // Compare against the end of the string for this one + // Don't care about cmdline name + char * expected_input_file = "RUN_test/unit_test.py"; + // Don't care about trick_version + char * expected_tag = "VSTestServer"; + + // Variables to be populated by the multicast message + char actual_hostname[80]; + unsigned short actual_port = 0; + char actual_username[80]; + int actual_pid = 0; + char actual_sim_dir[80]; + char actual_cmdline_name[80]; + char actual_input_file[80]; + char actual_trick_version[80]; + char actual_tag[80]; + unsigned short actual_duplicate_port = 0; + + while (!found && tries++ < max_multicast_tries) { + std::string broadcast_data = multicast_socket.receive(); + sscanf(broadcast_data.c_str(), "%s\t%hu\t%s\t%d\t%s\t%s\t%s\t%s\t%s\t%hu\n" , actual_hostname, &actual_port , + actual_username , &actual_pid , actual_sim_dir , actual_cmdline_name , + actual_input_file , actual_trick_version , actual_tag, &actual_duplicate_port) ; + + if (strcmp(actual_hostname, expected_hostname) == 0 && strcmp(expected_tag, actual_tag) == 0) { + found = true; + EXPECT_STREQ(actual_hostname, expected_hostname); + EXPECT_EQ(actual_port, expected_port); + EXPECT_STREQ(actual_username, expected_username); + EXPECT_GT(actual_pid, 0); + std::string expected_sim_dir_str(expected_sim_dir); + std::string actual_sim_dir_str(actual_sim_dir); + std::string end_of_actual = actual_sim_dir_str.substr(actual_sim_dir_str.length() - expected_sim_dir_str.length(), actual_sim_dir_str.length()); + EXPECT_EQ(expected_sim_dir_str, end_of_actual); + EXPECT_STREQ(actual_input_file, expected_input_file); + EXPECT_STREQ(actual_tag, expected_tag); + EXPECT_EQ(actual_duplicate_port, expected_port); + } + } + + if (!found) + FAIL() << "Multicast message never received"; +} + +#endif + TEST_F (VariableServerTest, Freeze) { if (socket_status != 0) { FAIL(); @@ -461,10 +977,6 @@ TEST_F (VariableServerTest, Freeze) { int mode; long long frame_count; long long freeze_frame_count; - - // Constants for clarity - const int MODE_RUN = 5; - const int MODE_FREEZE = 1; // lambda capture by refence is neat auto parse_message_for_sim_mode_and_frames = [&](const std::string& message) { @@ -539,7 +1051,6 @@ TEST_F (VariableServerTest, Freeze) { ASSERT_EQ(mode, MODE_RUN); } - TEST_F (VariableServerTest, CopyAndWriteModes) { if (socket_status != 0) { FAIL(); @@ -575,10 +1086,6 @@ TEST_F (VariableServerTest, CopyAndWriteModes) { vars = token; }; - auto spin = [&](int wait_cycles = 5) { - socket.receive(); - }; - // Check that every combination of modes is functional // Check that reasonable times and frames are returned as well // Default is VS_COPY_ASYNC=0 and VS_WRITE_ASYNC=0 @@ -602,7 +1109,7 @@ TEST_F (VariableServerTest, CopyAndWriteModes) { // With copy mode VS_COPY_SCHEDULED and write mode VS_WRITE_ASYNC, the first reply will be all 0 since the main time to copy has not occurred yet. // Is this what we want? Maybe we should have more strict communication on whether the data has been staged so the first message isn't incorrect - spin(); + // spin(); expected = "-1234 1234"; parse_message(socket.receive()); @@ -654,11 +1161,7 @@ TEST_F (VariableServerTest, CopyAndWriteModes) { socket << command; // Same issue as copy mode 1 write mode 0 - spin(); - // expected = "0 -1234567 123456789"; - // EXPECT_EQ(strcmp_IgnoringWhiteSpace(reply, expected), 0); - // std::cout << "\tExpected: " << expected << "\n\tActual: " << reply << std::endl; - + // spin(); parse_message(socket.receive()); expected = "-1234567 123456789"; EXPECT_EQ(strcmp_IgnoringWhiteSpace(vars, expected), 0) << "Received: " << vars << " Expected: " << expected; @@ -703,7 +1206,7 @@ TEST_F (VariableServerTest, CopyAndWriteModes) { socket.clear_buffered_data(); } -bool getCompleteBinaryMessage(ParsedBinaryMessage& message, Socket& socket) { +bool getCompleteBinaryMessage(ParsedBinaryMessage& message, Socket& socket, bool print = false) { static const int max_retries = 5; int tries = 0; @@ -722,15 +1225,162 @@ bool getCompleteBinaryMessage(ParsedBinaryMessage& message, Socket& socket) { } } + if (print) { + std::cout << "Message: "; + for (unsigned char byte : reply) { + std::cout << "0x" << std::setw(2) << std::setfill('0') << std::hex << (int)byte << ", "; + } + std::cout << std::endl; + } + return parse_success; } +TEST_F (VariableServerTest, send_stdio) { + if (socket_status != 0) { + FAIL(); + } + + socket << "trick.var_set_send_stdio(True)\n"; + socket << "sys.stdout.write(\"This message should redirect to varserver\")\n"; + + std::string message; + socket >> message; + + std::stringstream message_stream(message); + std::string token; + + int message_type; + int stream_num; + int text_size; + std::string text; + + message_stream >> message_type; + message_stream >> stream_num; + std::getline(message_stream, token, '\n'); + text_size = stoi(token); + std::getline(message_stream, text); + + if (text.size() != text_size) { + socket >> text; + } + + EXPECT_EQ (message_type, 4); + EXPECT_EQ (stream_num, 1); + EXPECT_EQ (text_size, 41); + + EXPECT_EQ(text, std::string("This message should redirect to varserver")); +} + + +#ifndef __APPLE__ + +TEST_F (VariableServerTest, MulticastAfterRestart) { + if (socket_status != 0) { + FAIL(); + } + + socket << "trick.var_server_set_user_tag(\"VSTestServer\")\n"; + + Socket multicast_socket; + if (multicast_socket.init_multicast("224.3.14.15", 9265) != 0) { + FAIL() << "Multicast Socket failed to initialize."; + } + + int max_multicast_tries = 100; + int tries = 0; + bool found = false; + + char expected_hostname[80]; + gethostname(expected_hostname, 80); + int expected_port = 40000; + + // get expected username + struct passwd *passp = getpwuid(getuid()) ; + char * expected_username; + if ( passp == NULL ) { + expected_username = strdup("unknown") ; + } else { + expected_username = strdup(passp->pw_name) ; + } + + // Don't care about PID, just check that it's > 0 + char * expected_sim_dir = "trick/test/SIM_test_varserv"; // Compare against the end of the string for this one + // Don't care about cmdline name + char * expected_input_file = "RUN_test/unit_test.py"; + // Don't care about trick_version + char * expected_tag = "VSTestServer"; + + // Variables to be populated by the multicast message + char actual_hostname[80]; + unsigned short actual_port = 0; + char actual_username[80]; + int actual_pid = 0; + char actual_sim_dir[80]; + char actual_cmdline_name[80]; + char actual_input_file[80]; + char actual_trick_version[80]; + char actual_tag[80]; + unsigned short actual_duplicate_port = 0; + + while (!found && tries++ < max_multicast_tries) { + std::string broadcast_data = multicast_socket.receive(); + sscanf(broadcast_data.c_str(), "%s\t%hu\t%s\t%d\t%s\t%s\t%s\t%s\t%s\t%hu\n" , actual_hostname, &actual_port , + actual_username , &actual_pid , actual_sim_dir , actual_cmdline_name , + actual_input_file , actual_trick_version , actual_tag, &actual_duplicate_port) ; + + if (strcmp(actual_hostname, expected_hostname) == 0 && strcmp(expected_tag, actual_tag) == 0) { + found = true; + EXPECT_STREQ(actual_hostname, expected_hostname); + EXPECT_EQ(actual_port, expected_port); + EXPECT_STREQ(actual_username, expected_username); + EXPECT_GT(actual_pid, 0); + std::string expected_sim_dir_str(expected_sim_dir); + std::string actual_sim_dir_str(actual_sim_dir); + std::string end_of_actual = actual_sim_dir_str.substr(actual_sim_dir_str.length() - expected_sim_dir_str.length(), actual_sim_dir_str.length()); + EXPECT_EQ(expected_sim_dir_str, end_of_actual); + EXPECT_STREQ(actual_input_file, expected_input_file); + EXPECT_STREQ(actual_tag, expected_tag); + EXPECT_EQ(actual_duplicate_port, expected_port); + } + } + + if (!found) + FAIL() << "Multicast message never received"; +} + +#endif + TEST_F (VariableServerTest, Binary) { if (socket_status != 0) { FAIL(); } + + socket << "trick.var_binary()\ntrick.var_add(\"vsx.vst.n\")\n"; + ParsedBinaryMessage message_n; + + if (!getCompleteBinaryMessage(message_n, socket)) { + FAIL() << "Parser was not able to interpret the message."; + } + + Var arr_var = message_n.getVariable("vsx.vst.n"); + EXPECT_EQ(arr_var.getArraySize(), 5); + std::vector raw_arr = arr_var.getRawBytes(); + std::vector values; + for (unsigned int i = 0; i < raw_arr.size(); i += 4) { + int val = 0; + for (unsigned int j = i; j < i+4; j++) { + val |= raw_arr[j] << j*8; + } + values.push_back(val); + } + + std::vector expected_arr = {0, 1, 2, 3, 4}; + EXPECT_EQ(values, expected_arr); + + std::vector reply; - socket << "trick.var_binary()\ntrick.var_add(\"vsx.vst.c\")\ntrick.var_add(\"vsx.vst.k\")\ntrick.var_add(\"vsx.vst.o\")\ntrick.var_add(\"vsx.vst.m\")\n"; + socket << "trick.var_clear()\ntrick.var_add(\"vsx.vst.c\")\ntrick.var_add(\"vsx.vst.k\")\ntrick.var_add(\"vsx.vst.o\")\ntrick.var_add(\"vsx.vst.m\")\n"; ParsedBinaryMessage message; @@ -792,7 +1442,6 @@ TEST_F (VariableServerTest, Binary) { } TEST_F (VariableServerTest, DISABLED_BinaryByteswap) { - // TODO: VAR_BYTESWAP DOES NOT APPEAR TO WORK CORRECTLY std::vector reply; socket << "trick.var_binary()\ntrick.var_byteswap(False)\ntrick.var_add(\"vsx.vst.f\")\ntrick.var_add(\"vsx.vst.j\")\n"; @@ -802,11 +1451,11 @@ TEST_F (VariableServerTest, DISABLED_BinaryByteswap) { // Test byteswap - std::cout << "Message: "; - for (unsigned char byte : reply) { - std::cout << "0x" << std::setw(2) << std::setfill('0') << std::hex << (int)byte << " "; - } - std::cout << std::endl; + // std::cout << "Message: "; + // for (unsigned char byte : reply) { + // std::cout << "0x" << std::setw(2) << std::setfill('0') << std::hex << (int)byte << " "; + // } + // std::cout << std::endl; ParsedBinaryMessage message; @@ -832,11 +1481,11 @@ TEST_F (VariableServerTest, DISABLED_BinaryByteswap) { socket.receive_bytes(); reply = socket.receive_bytes(); - std::cout << "Message: "; - for (unsigned char byte : reply) { - std::cout << "0x" << std::setw(2) << std::setfill('0') << std::hex << (int)byte << " "; - } - std::cout << std::endl; + // std::cout << "Message: "; + // for (unsigned char byte : reply) { + // std::cout << "0x" << std::setw(2) << std::setfill('0') << std::hex << (int)byte << " "; + // } + // std::cout << std::endl; ParsedBinaryMessage byteswap_message(true, false); @@ -852,7 +1501,6 @@ TEST_F (VariableServerTest, DISABLED_BinaryByteswap) { EXPECT_EQ(strcmp(byteswap_message.variables[0].getName().c_str(), "vsx.vst.f"), 0); EXPECT_EQ(byteswap_message.variables[0].getType(), TRICK_UNSIGNED_INTEGER); EXPECT_EQ(byteswap_message.variables[0].getValue(), 123456); - std::cout << "Byteswap value: " << byteswap_message.variables[0].getValue() << std::endl; EXPECT_EQ(strcmp(byteswap_message.variables[1].getName().c_str(), "vsx.vst.j"), 0); EXPECT_EQ(byteswap_message.variables[1].getType(), TRICK_DOUBLE); diff --git a/test/SIM_test_varserv/models/varserv/include/VS.hh b/test/SIM_test_varserv/models/varserv/include/VS.hh index 20ca8971..5aa7c205 100644 --- a/test/SIM_test_varserv/models/varserv/include/VS.hh +++ b/test/SIM_test_varserv/models/varserv/include/VS.hh @@ -31,7 +31,9 @@ class VSTest { int n[5]; std::string o; char * p; - wchar_t * q; + wchar_t * q; /**< trick_chkpnt_io(**) */ + + int large_arr[4000]; int status; diff --git a/test/SIM_test_varserv/models/varserv/src/VS.cpp b/test/SIM_test_varserv/models/varserv/src/VS.cpp index fb859e73..d9d2460f 100644 --- a/test/SIM_test_varserv/models/varserv/src/VS.cpp +++ b/test/SIM_test_varserv/models/varserv/src/VS.cpp @@ -4,7 +4,8 @@ REFERENCE: ( None ) ASSUMPTIONS AND LIMITATIONS: ( None ) CLASS: ( scheduled ) LIBRARY DEPENDENCY: ( VS.o ) -PROGRAMMERS: ( (Lindsay Landry) (L3) (9-12-2013) (Jackie Deans) (CACI) (11-30-2022) ) +PROGRAMMERS: ( (Lindsay Landry) (L3) (9-12-2013) + (Jackie Deans) (CACI) (11-30-2022) ) *******************************************************************************/ #include #include "../include/VS.hh" @@ -35,6 +36,10 @@ int VSTest::default_vars() { o = std::string("You 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."); p = "I 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?"; q = L"This 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."; + + for (int i = 0; i < 4000; i++) { + large_arr[i] = i; + } } int VSTest::init() { @@ -44,10 +49,12 @@ int VSTest::init() { int VSTest::fail() { status = 1; + return 0; } int VSTest::success() { - status = 0; + status = 0; + return 0; } int VSTest::shutdown() { diff --git a/trick_source/sim_services/VariableServer/VariableServerListenThread.cpp b/trick_source/sim_services/VariableServer/VariableServerListenThread.cpp index 17408e38..34e4a125 100644 --- a/trick_source/sim_services/VariableServer/VariableServerListenThread.cpp +++ b/trick_source/sim_services/VariableServer/VariableServerListenThread.cpp @@ -106,7 +106,10 @@ int Trick::VariableServerListenThread::check_and_move_listen_device() { } void Trick::VariableServerListenThread::create_tcp_socket(const char * address, unsigned short in_port ) { - tc_init_with_connection_info(&listen_dev, AF_INET, SOCK_STREAM, address, in_port) ; + int result = tc_init_with_connection_info(&listen_dev, AF_INET, SOCK_STREAM, address, in_port) ; + if (result != 0) { + message_publish(MSG_ERROR, "ERROR: Could not establish additional listen port at address %s and port %d for Variable Server.\n", address, in_port); + } } void * Trick::VariableServerListenThread::thread_body() { diff --git a/trick_source/trick_utils/var_binary_parser/src/var_binary_parser.cc b/trick_source/trick_utils/var_binary_parser/src/var_binary_parser.cc index 30d3ac4a..4255efa8 100644 --- a/trick_source/trick_utils/var_binary_parser/src/var_binary_parser.cc +++ b/trick_source/trick_utils/var_binary_parser/src/var_binary_parser.cc @@ -5,168 +5,6 @@ #include -// Utility method -uint64_t bytesToInt(const std::vector& bytes, bool byteswap) { - uint64_t result = 0; - - if (byteswap) { - for (unsigned int i = 0; i < bytes.size(); i++) { - result |= bytes[bytes.size() - 1 - i] << i*8; - } - } else { - for (unsigned int i = 0; i < bytes.size(); i++) { - result |= bytes[i] << i*8; - } - } - - return result; -} - -class MessageIterator { - public: - MessageIterator(const std::vector& container) : _container(container), _index(0) {} - - std::vector slice(unsigned int length) { - unsigned int slice_end = _index + length; - if (_index > _container.size() || slice_end > _container.size()) { - throw MalformedMessageException("Message ends unexpectedly"); - } - - return std::vector(_container.begin() + _index, _container.begin() + slice_end); - } - - void operator+= (int n) { - _index += n; - } - - private: - std::vector _container; - unsigned int _index; -}; - -/************************************************************************** - * Var implementation -**************************************************************************/ - -const size_t ParsedBinaryMessage::header_size = 12; -const size_t ParsedBinaryMessage::message_indicator_size = 4; -const size_t ParsedBinaryMessage::variable_num_size = 4; -const size_t ParsedBinaryMessage::message_size_size = 4; -const size_t ParsedBinaryMessage::variable_name_length_size = 4; -const size_t ParsedBinaryMessage::variable_type_size = 4; -const size_t ParsedBinaryMessage::variable_size_size = 4; - -int ParsedBinaryMessage::parse (const std::vector& bytes){ - if (bytes.size() < header_size) { - throw MalformedMessageException(std::string("Not enough bytes in message to contain header: expected at least 12, got " + std::to_string(bytes.size()))); - } - - MessageIterator messageIterator (bytes); - - // First 4 bytes is message type - _message_type = bytesToInt(messageIterator.slice(message_indicator_size), _byteswap); - if (!validateMessageType(_message_type)) { - throw MalformedMessageException(std::string("Received unknown message type: " + std::to_string(_message_type))); - } - - messageIterator += message_indicator_size; - - // Next 4 bytes is message size - _message_size = bytesToInt(messageIterator.slice(message_size_size), _byteswap); - if (bytes.size() - message_indicator_size != _message_size) { - std::string error_message = "Message size in header (" + std::to_string(_message_size) + ") does not match size of message received (" + std::to_string(bytes.size() - message_indicator_size) + ")"; - throw MalformedMessageException(error_message); - } - - messageIterator += message_size_size; - - // Next 4 bytes is number of variables - _num_vars = bytesToInt(messageIterator.slice(variable_num_size), _byteswap); - messageIterator += variable_num_size; - - // Pull out all of the variables - for (unsigned int i = 0; i < _num_vars; i++) { - Var variable; - - if (!_nonames) { - // Get the name - size_t name_length = bytesToInt(messageIterator.slice(variable_name_length_size), _byteswap); - messageIterator += variable_name_length_size; - - variable.setName(name_length, messageIterator.slice(name_length)); - messageIterator += name_length; - } - - // Parse the type first - int var_type = bytesToInt(messageIterator.slice(variable_type_size), _byteswap); - messageIterator += variable_type_size; - - size_t var_size = bytesToInt(messageIterator.slice(variable_size_size), _byteswap); - messageIterator += variable_size_size; - - variable.setValue(messageIterator.slice(var_size), var_size, static_cast(var_type), _byteswap); - messageIterator += var_size; - - variables.emplace_back(variable); - } - - return 0; -} - -void ParsedBinaryMessage::combine (const ParsedBinaryMessage& other) { - if (_message_type != other._message_type) { - std::string error_message = "Trying to combine two messages with different message indicators (" + std::to_string(_message_type) + " and " + std::to_string(other._message_type) + ")"; - throw MalformedMessageException(error_message); - } - - // Combined size - subtract the header size from other message size - _message_size += other._message_size - message_size_size - variable_num_size; - - // Combine variables - _num_vars += other._num_vars; - variables.insert(variables.end(), other.variables.begin(), other.variables.end()); - - // Other error checking - duplicate variables? -} - -Var ParsedBinaryMessage::getVariable(const std::string& name) { - if (_nonames) - throw IncorrectUsageException("Cannot fetch variables by name in noname message"); - - for (auto variable : variables) { - if (variable.getName() == name) - return variable; - } - - throw IncorrectUsageException("Variable " + name + " does not exist in this message."); -} - -Var ParsedBinaryMessage::getVariable(unsigned int index) { - if (index >= variables.size()) { - throw IncorrectUsageException("Variable index " + std::to_string(index) + " does not exist in this message."); - } - - return variables[index]; -} - -int ParsedBinaryMessage::getMessageType() const { - return _message_type; -} - -unsigned int ParsedBinaryMessage::getMessageSize() const { - return _message_size; -} - -unsigned int ParsedBinaryMessage::getNumVars() const { - return _num_vars; -} - -// Static methods - -bool ParsedBinaryMessage::validateMessageType(int message_type) { - return message_type >= VS_MIN_CODE && message_type <= VS_MAX_CODE; -} - /************************************************************************** * Var implementation **************************************************************************/ @@ -221,6 +59,24 @@ TRICK_TYPE Var::getType() const { return _trick_type; } +std::vector Var::getRawBytes() const { + return value_bytes; +} + +int Var::getArraySize() const { + auto val_size_pair = type_size_map.find(_trick_type); + if (val_size_pair == type_size_map.end()) { + return -1; + } + + int val_size = val_size_pair->second; + if (_var_size % val_size != 0) { + return -1; + } + + return _var_size / val_size; +} + // Template specialization for each supported type template<> @@ -362,3 +218,185 @@ wchar_t Var::getValue () const { return getInterpreter().wchar_val; } + + + +// Utility method +uint64_t bytesToInt(const std::vector& bytes, bool byteswap) { + uint64_t result = 0; + + if (byteswap) { + for (unsigned int i = 0; i < bytes.size(); i++) { + result |= bytes[bytes.size() - 1 - i] << i*8; + } + } else { + for (unsigned int i = 0; i < bytes.size(); i++) { + result |= bytes[i] << i*8; + } + } + + return result; +} + +class MessageIterator { + public: + MessageIterator(const std::vector& container) : _container(container), _index(0) {} + + std::vector slice(unsigned int length) { + unsigned int slice_end = _index + length; + if (_index > _container.size() || slice_end > _container.size()) { + throw MalformedMessageException("Message ends unexpectedly"); + } + + return std::vector(_container.begin() + _index, _container.begin() + slice_end); + } + + void operator+= (int n) { + _index += n; + } + + private: + std::vector _container; + unsigned int _index; +}; + +/************************************************************************** + * Var implementation +**************************************************************************/ + +const size_t ParsedBinaryMessage::header_size = 12; +const size_t ParsedBinaryMessage::message_indicator_size = 4; +const size_t ParsedBinaryMessage::variable_num_size = 4; +const size_t ParsedBinaryMessage::message_size_size = 4; +const size_t ParsedBinaryMessage::variable_name_length_size = 4; +const size_t ParsedBinaryMessage::variable_type_size = 4; +const size_t ParsedBinaryMessage::variable_size_size = 4; + +// Sometimes, we need to reconstruct the size from the message. +// Risk of segfaulting here if the bytes are not correctly formed +int ParsedBinaryMessage::parse (char * raw_bytes) { + std::vector bytes; + unsigned int i = 0; + for ( ; i < message_indicator_size + message_size_size; i++) { + bytes.push_back(raw_bytes[i]); + } + + unsigned int message_size = bytesToInt(std::vector (bytes.begin() + message_indicator_size, bytes.begin() + message_indicator_size + message_size_size), _byteswap); + for ( ; i < message_size + message_indicator_size; i++) { + bytes.push_back(raw_bytes[i]); + } + + return parse(bytes); +} + + +int ParsedBinaryMessage::parse (const std::vector& bytes) { + if (bytes.size() < header_size) { + throw MalformedMessageException(std::string("Not enough bytes in message to contain header: expected at least 12, got " + std::to_string(bytes.size()))); + } + + MessageIterator messageIterator (bytes); + + // First 4 bytes is message type + _message_type = bytesToInt(messageIterator.slice(message_indicator_size), _byteswap); + if (!validateMessageType(_message_type)) { + throw MalformedMessageException(std::string("Received unknown message type: " + std::to_string(_message_type))); + } + + messageIterator += message_indicator_size; + + // Next 4 bytes is message size + _message_size = bytesToInt(messageIterator.slice(message_size_size), _byteswap); + if (bytes.size() - message_indicator_size != _message_size) { + std::string error_message = "Message size in header (" + std::to_string(_message_size) + ") does not match size of message received (" + std::to_string(bytes.size() - message_indicator_size) + ")"; + throw MalformedMessageException(error_message); + } + + messageIterator += message_size_size; + + // Next 4 bytes is number of variables + _num_vars = bytesToInt(messageIterator.slice(variable_num_size), _byteswap); + messageIterator += variable_num_size; + + // Pull out all of the variables + for (unsigned int i = 0; i < _num_vars; i++) { + Var variable; + + if (!_nonames) { + // Get the name + size_t name_length = bytesToInt(messageIterator.slice(variable_name_length_size), _byteswap); + messageIterator += variable_name_length_size; + + variable.setName(name_length, messageIterator.slice(name_length)); + messageIterator += name_length; + } + + // Parse the type first + int var_type = bytesToInt(messageIterator.slice(variable_type_size), _byteswap); + messageIterator += variable_type_size; + + size_t var_size = bytesToInt(messageIterator.slice(variable_size_size), _byteswap); + messageIterator += variable_size_size; + + variable.setValue(messageIterator.slice(var_size), var_size, static_cast(var_type), _byteswap); + messageIterator += var_size; + + variables.emplace_back(variable); + } + + return 0; +} + +void ParsedBinaryMessage::combine (const ParsedBinaryMessage& other) { + if (_message_type != other._message_type) { + std::string error_message = "Trying to combine two messages with different message indicators (" + std::to_string(_message_type) + " and " + std::to_string(other._message_type) + ")"; + throw MalformedMessageException(error_message); + } + + // Combined size - subtract the header size from other message size + _message_size += other._message_size - message_size_size - variable_num_size; + + // Combine variables + _num_vars += other._num_vars; + variables.insert(variables.end(), other.variables.begin(), other.variables.end()); + + // Other error checking - duplicate variables? +} + +Var ParsedBinaryMessage::getVariable(const std::string& name) { + if (_nonames) + throw IncorrectUsageException("Cannot fetch variables by name in noname message"); + + for (auto variable : variables) { + if (variable.getName() == name) + return variable; + } + + throw IncorrectUsageException("Variable " + name + " does not exist in this message."); +} + +Var ParsedBinaryMessage::getVariable(unsigned int index) { + if (index >= variables.size()) { + throw IncorrectUsageException("Variable index " + std::to_string(index) + " does not exist in this message."); + } + + return variables[index]; +} + +int ParsedBinaryMessage::getMessageType() const { + return _message_type; +} + +unsigned int ParsedBinaryMessage::getMessageSize() const { + return _message_size; +} + +unsigned int ParsedBinaryMessage::getNumVars() const { + return _num_vars; +} + +// Static methods + +bool ParsedBinaryMessage::validateMessageType(int message_type) { + return message_type >= VS_MIN_CODE && message_type <= VS_MAX_CODE; +} diff --git a/trick_source/trick_utils/var_binary_parser/test/TEST_var_binary_parser.cc b/trick_source/trick_utils/var_binary_parser/test/TEST_var_binary_parser.cc index f0a3ffe8..1ea19406 100644 --- a/trick_source/trick_utils/var_binary_parser/test/TEST_var_binary_parser.cc +++ b/trick_source/trick_utils/var_binary_parser/test/TEST_var_binary_parser.cc @@ -616,6 +616,35 @@ TEST (BinaryParserTest, GetVarByIndexWrong) { } } +TEST (BinaryParserTest, ArrayTest) { + // Message: vst.vsx.n + std::vector bytes = {0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00}; + ParsedBinaryMessage message(false, true); + + try { + message.parse(bytes); + } catch (const std::exception& ex) { + FAIL() << "Exception thrown: " << ex.what(); + } + + ASSERT_EQ(message.getNumVars(), 1); + Var arr_var = message.getVariable(0); + EXPECT_EQ(arr_var.getArraySize(), 5); + std::vector raw_arr = arr_var.getRawBytes(); + std::vector values; + for (unsigned int i = 0; i < raw_arr.size(); i += 4) { + int val = 0; + for (unsigned int j = i; j < i+4; j++) { + val |= raw_arr[j] << j*8; + } + values.push_back(val); + } + + std::vector expected = {0, 1, 2, 3, 4}; + EXPECT_EQ(values, expected); +} + + /************************************************************************** * Var parsing type tests **************************************************************************/ diff --git a/trickops.py b/trickops.py index f1c58581..2f715f21 100644 --- a/trickops.py +++ b/trickops.py @@ -28,17 +28,20 @@ class SimTestWorkflow(TrickWorkflow): # This job in SIM_stls dumps a checkpoint that is then read in and checked by RUN_test/unit_test.py in the same sim # This is a workaround to ensure that this run goes first. first_phase_jobs = [] - stl_dump_job = self.get_sim('SIM_stls').get_run(input='RUN_test/setup.py').get_run_job() - first_phase_jobs.append(stl_dump_job) - run_jobs.remove(stl_dump_job) + stl_sim = self.get_sim('SIM_stls') + if stl_sim is not None: + stl_dump_job = stl_sim.get_run(input='RUN_test/setup.py').get_run_job() + first_phase_jobs.append(stl_dump_job) + run_jobs.remove(stl_dump_job) # Same with SIM_checkpoint_data_recording - half the runs dump checkpoints, the others read and verify. # Make sure that the dump checkpoint runs go first. - num_dump_runs = int(len(self.get_sim('SIM_checkpoint_data_recording').get_runs())/2) - for i in range(num_dump_runs): - job = self.get_sim('SIM_checkpoint_data_recording').get_run(input=('RUN_test' + str(i+1) + '/dump.py')).get_run_job() - first_phase_jobs.append(job) - run_jobs.remove(job) + if self.get_sim('SIM_checkpoint_data_recording') is not None: + num_dump_runs = int(len(self.get_sim('SIM_checkpoint_data_recording').get_runs())/2) + for i in range(num_dump_runs): + job = self.get_sim('SIM_checkpoint_data_recording').get_run(input=('RUN_test' + str(i+1) + '/dump.py')).get_run_job() + first_phase_jobs.append(job) + run_jobs.remove(job) # Some tests fail intermittently for reasons not related to the tests themselves, mostly network weirdness. # Allow retries so that we can still cover some network-adjacent code