summaryrefslogtreecommitdiff
path: root/ACE/bin/LabVIEW_RT/labview_test_controller/labview_test_controller.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'ACE/bin/LabVIEW_RT/labview_test_controller/labview_test_controller.cpp')
-rw-r--r--ACE/bin/LabVIEW_RT/labview_test_controller/labview_test_controller.cpp614
1 files changed, 614 insertions, 0 deletions
diff --git a/ACE/bin/LabVIEW_RT/labview_test_controller/labview_test_controller.cpp b/ACE/bin/LabVIEW_RT/labview_test_controller/labview_test_controller.cpp
new file mode 100644
index 00000000000..e8fc7c80550
--- /dev/null
+++ b/ACE/bin/LabVIEW_RT/labview_test_controller/labview_test_controller.cpp
@@ -0,0 +1,614 @@
+// $Id$
+//
+// Defines the entry point for the LabVIEW RT test controller DLL application.
+// This DLL is loaded at system boot by LabVIEW RT. The controller waits for
+// TCP connections from the ACE+TAO test scripts. The test scripts will direct
+// operation of the tests via commands sent over TCP. In order to be ready for
+// connections without intervention via VI, the initial load will spawn a
+// thread that sets up the listening socket.
+
+#include "stdafx.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <io.h>
+#include <memory.h>
+#include <process.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <Winsock2.h>
+
+// NULL is the documented way to check DLL handles, and this is plain
+// Windows code, not ACE, so we stick to the Microsoft way...
+// FUZZ: disable check_for_NULL
+
+// This is plain Windows code, not ACE. Therefore we disable
+// the check for ACE_OS
+// FUZZ: disable check_for_lack_ACE_OS
+
+// TEST_FUNC is the prototype for the called test's main entrypoint. It's
+// the normal C main.
+typedef int (*TEST_FUNC) (int argc, char *argv[]);
+
+// Thread entrypoints
+static unsigned int __stdcall test_control (void *param);
+static unsigned int __stdcall peer_svc (void *peer_p);
+static unsigned int __stdcall run_test (void *test_p);
+
+static const char *format_errmsg (unsigned int errcode, const char *prefix);
+
+// Logging information
+static const char *LogName = "acetao.log";
+static HANDLE logf = INVALID_HANDLE_VALUE;
+
+BOOL APIENTRY DllMain( HANDLE hModule,
+ DWORD ul_reason_for_call,
+ LPVOID lpReserved
+ )
+{
+ if (ul_reason_for_call == DLL_PROCESS_ATTACH)
+ {
+ return (0 != _beginthreadex (0, // security
+ 8 * 1024, // stack size
+ test_control, // entrypoint
+ 0, // param
+ 0, // creation flags
+ 0)); // ptr to thread id
+ }
+
+ return TRUE;
+}
+
+class Test
+{
+public:
+ Test () : dll_handle_ (NULL),
+ thr_handle_ (0),
+ entry_ (0),
+ running_ (false),
+ status_ (-1)
+ {}
+ ~Test ();
+
+ HANDLE handle (void) { return this->thr_handle_; }
+ int run (void);
+ const char *start (const char *name);
+ bool status (int *exit_status);
+ int wait (void);
+ void kill (void);
+
+ // Clean up remnants of a test run.
+ void cleanup (void);
+
+private:
+ HMODULE dll_handle_;
+ HANDLE thr_handle_;
+ TEST_FUNC entry_;
+ bool running_;
+ int status_;
+ enum { CMDLINE_LEN = 1024, ARGV_SIZE = 100 };
+ char name_[CMDLINE_LEN];
+ char cmdline_[CMDLINE_LEN];
+ int argc_;
+ char *argv_[ARGV_SIZE];
+};
+
+class Peer
+{
+public:
+ Peer (SOCKET h) : handle_ (h) {}
+
+ // Run the Peer's session; intended to be called from a new thread devoted
+ // to this peer's session.
+ int svc (void);
+
+private:
+ Peer () {};
+
+ // Process command input from socket.
+ int command (void);
+
+ // Send a reply string to the peer.
+ int reply (const char *msg);
+
+ SOCKET handle_;
+ Test test_;
+};
+
+// Run a peer session; assume there's a thread for each session, so this
+// object has all it needs for context located in 'this' object, and it can
+// block at any point as long as one remembers that there is one or more
+// test threads running and some attention must be paid to the encapsulated
+// socket handle over which this object receives control commands from the
+// host test driver.
+int
+Peer::svc (void)
+{
+ // Read commands until EOF (peer closed) or protocol error
+ while (0 == this->command ())
+ ;
+ closesocket (this->handle_);
+ this->handle_ = INVALID_SOCKET;
+ return 0;
+}
+
+int
+Peer::command (void)
+{
+ // The protocol exchanges with the peer are execpted to be lock-step
+ // request-reply command lines, so we can make assumptions about a complete
+ // line being available to make life easier.
+ const int MAX_RECV = 1024;
+ char line[MAX_RECV], *p;
+ p = &line[0];
+ int count = 0, len = 0;
+ while ((count = recv (this->handle_, p, MAX_RECV - len, 0)) > 0)
+ {
+ p[count] = '\0';
+ len += count;
+ p += count;
+ char *nl;
+ if ((nl = strchr (line, '\n')) == 0)
+ continue;
+
+ // At this point we have a 0-terminated string with a newline ending
+ // the command line. Break out and process the command.
+ break;
+ }
+ if (count <= 0)
+ return -1; // Relay closed/error socket to caller
+
+ char *cmd = strtok (line, "\t \n\r");
+ if (cmd == 0)
+ {
+ char err[1024];
+ sprintf (err, "Can't parse input: %s\n", line);
+ this->reply (err);
+ return -1;
+ }
+ // Which command is it? These commands are known:
+ //
+ // run <test-dll> [args]
+ // Run test in the named test-dll; respond with "OK" or an error string.
+ // status
+ // If test still running return "RUNNING" else return exit status.
+ // wait
+ // Wait for test to exit; return "OK"
+ // kill
+ // Kill the thread with the most recent test; return "OK".
+ // snaplog
+ // Take a snapshot of the current stdout/stderr log to a new file
+ // name and reset the stdout/stderr log.
+ if (strcmp ("run", cmd) == 0)
+ {
+ char *test = strtok (0, "\t \n\r");
+ if (test == 0)
+ {
+ this->reply ("Malformed run command\n");
+ return -1;
+ }
+ // start() pulls apart the rest of the command line...
+ const char *errmsg = this->test_.start (test);
+ if (errmsg == 0)
+ this->reply ("OK\n");
+ else
+ this->reply (errmsg);
+ }
+ else if (strcmp ("status", cmd) == 0)
+ {
+ int status;
+ if (this->test_.status (&status))
+ {
+ char retvalmsg[64];
+ sprintf (retvalmsg, "%d\n", status);
+ this->reply (retvalmsg);
+ }
+ else
+ this->reply ("RUNNING\n");
+ }
+ else if (strcmp ("wait", cmd) == 0)
+ {
+ int status = this->test_.wait ();
+ char retvalmsg[64];
+ sprintf (retvalmsg, "%d\n", status);
+ this->reply (retvalmsg);
+ }
+ else if (strcmp ("kill", cmd) == 0)
+ {
+ // Killing things is bad... say we can't and the host should reboot us.
+ this->reply ("NO - please reboot me\n");
+ }
+ else if (strcmp ("waitforfile", cmd) == 0)
+ {
+ char *name = strtok (0, "\t \n\r");
+ if (name == 0)
+ {
+ this->reply ("Malformed waitforfile command\n");
+ return -1;
+ }
+ char *secs_s = strtok (0, "\t \n\r");
+ int secs = 0;
+ if (secs_s == 0 || (secs = atoi (secs_s)) <= 0)
+ {
+ this->reply ("Malformed waitforfile command\n");
+ return -1;
+ }
+ struct _stat info;
+ const char *msg = 0;
+ bool found = false;
+ while (secs > 0)
+ {
+ if (_stat (name, &info) == -1) // No file yet
+ {
+ if (errno != ENOENT)
+ {
+ // Something more serious than no file yet; bail out.
+ msg = format_errmsg (errno, name);
+ break;
+ }
+ }
+ else
+ {
+ if (info.st_size > 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ // Either no file yet, or it's there but with no content yet.
+ Sleep (1 * 1000); // arg is in msec
+ --secs;
+ }
+ if (found)
+ this->reply ("OK\n");
+ else if (secs == 0)
+ this->reply ("TIMEOUT\n");
+ else
+ this->reply (msg);
+ }
+ else if (strcmp ("snaplog", cmd) == 0)
+ {
+ if (logf == INVALID_HANDLE_VALUE)
+ {
+ this->reply ("NONE\n");
+ }
+ else
+ {
+ CloseHandle (logf);
+ if (0 == rename (LogName, "snapshot.txt"))
+ {
+ char abspath[1024];
+ if (_fullpath (abspath, "snapshot.txt", 1024))
+ {
+ strcat (abspath, "\n");
+ this->reply (abspath);
+ }
+ else
+ {
+ // Last ditch effort to get a name back to the client
+ this->reply ("\\ni-rt\\system\\snapshot.txt\n");
+ }
+ }
+ else
+ {
+ this->reply ("NONE\n");
+ }
+ // Reset stdout/stderr to a new file
+ logf = CreateFile (LogName,
+ FILE_ALL_ACCESS,
+ FILE_SHARE_READ,
+ 0, // security
+ CREATE_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL,
+ 0);
+ SetStdHandle (STD_OUTPUT_HANDLE, logf);
+ SetStdHandle (STD_ERROR_HANDLE, logf);
+ }
+ }
+ else
+ {
+ this->reply ("Unrecognized command\n");
+ return -1;
+ }
+ return 0;
+}
+
+int
+Peer::reply (const char *msg)
+{
+ int len = (int)strlen (msg); // size_t -> int
+ return send (this->handle_, msg, len, 0) > 0 ? 0 : -1;
+}
+
+Test::~Test ()
+{
+ this->cleanup ();
+}
+
+int
+Test::run (void)
+{
+ this->running_ = true;
+ try
+ {
+ this->status_ = (this->entry_) (this->argc_, this->argv_);
+ }
+ catch (...)
+ {
+ // Try to note this exception then save the log file before bailing out.
+ DWORD bl;
+ char msg[256];
+ sprintf (msg, "Exception in %s caught by labview_test_controller\n",
+ this->name_);
+ WriteFile (logf, msg, (DWORD)strlen(msg), &bl, 0);
+ FlushFileBuffers (logf);
+ CloseHandle (logf);
+ throw;
+ }
+ this->running_ = false;
+ // It's possible to cleanup() here; however, that would introduce a race
+ // with start() following beginthreadex(). So do all the cleanup on user
+ // action - either getting status, waiting, killing, or running another
+ // test. Or, terminating the connection.
+ return 0;
+}
+
+const char *
+Test::start (const char *name)
+{
+ if (this->running_)
+ return "Already running\n";
+
+ const char *msg = 0;
+
+ // Reset test status to not inadvertantly report a previous test.
+ this->status_ = -1;
+ this->cleanup (); // Resets cmdline_, argc_, argv_
+
+ // The command line is part-way through being tokenized by strtok(). It
+ // left off after the program name. Anything remaining are the command
+ // line arguments for the program. Pick off whatever is there, copy it
+ // to the cmdline_ array and fill in argc_/argv_ for the eventual run.
+ strcpy (this->name_, name);
+ this->argv_[0] = this->name_;
+ this->argc_ = 1;
+ size_t cmdchars = 0;
+ for (char *token = strtok (0, "\t \n\r");
+ token != 0 && (cmdchars + strlen (token) + 1) < CMDLINE_LEN;
+ token = strtok (0, "\t \n\r"))
+ {
+ // We have a new token and it will fit in cmdline_. Copy it to the
+ // next spot in cmdline_, add it to argv_/argc_ then update cmdchars
+ // to account for the copied-in token and its nul terminator.
+ strcpy (&this->cmdline_[cmdchars], token);
+ this->argv_[this->argc_] = &this->cmdline_[cmdchars];
+ ++this->argc_;
+ cmdchars += (strlen (token) + 1);
+ }
+ char libspec[1024];
+ sprintf (libspec, "%s.dll", name);
+ if ((this->dll_handle_ = LoadLibrary (libspec)) == NULL)
+ return format_errmsg (GetLastError (), libspec);
+
+ this->entry_ = (TEST_FUNC) GetProcAddress (this->dll_handle_, "main");
+ if (this->entry_ == NULL)
+ {
+ msg = format_errmsg (GetLastError (), "main");
+ this->cleanup ();
+ return msg;
+ }
+ else
+ {
+ unsigned int thread_id; /* unused */
+ uintptr_t h = _beginthreadex (0, // security
+ 1024 * 1024, // stack size
+ run_test, // entrypoint
+ (void *)this, // arglist
+ 0, // initflag
+ &thread_id); // thread ID
+ this->thr_handle_ = (HANDLE) h;
+ if (h == 0) // Test thread may have access to thr_handle_
+ {
+ msg = format_errmsg (GetLastError (), "spawn");
+ this->cleanup ();
+ return msg;
+ }
+ }
+ return 0;
+}
+
+bool
+Test::status (int *exit_status)
+{
+ if (this->running_)
+ return false; // still running
+
+ *exit_status = this->status_;
+ this->cleanup ();
+ return true;
+}
+
+int
+Test::wait (void)
+{
+ WaitForSingleObject (this->thr_handle_, INFINITE);
+ if (!this->running_)
+ this->cleanup ();
+ return this->status_;
+}
+
+void
+Test::kill (void)
+{
+ TerminateThread (this->thr_handle_, -1);
+ this->cleanup ();
+ this->running_ = false;
+ this->status_ = -1;
+}
+
+// Clean up remnants of a test run.
+void
+Test::cleanup (void)
+{
+ if (this->dll_handle_ != NULL)
+ {
+ FreeLibrary (this->dll_handle_);
+ this->dll_handle_ = NULL;
+ }
+ if (this->thr_handle_ != 0)
+ {
+ CloseHandle (this->thr_handle_);
+ this->thr_handle_ = 0;
+ }
+ this->entry_ = 0;
+ this->argc_ = 0;
+ for (int i = 0; i < ARGV_SIZE; ++i)
+ this->argv_[i] = 0;
+ memset (this->cmdline_, 0, CMDLINE_LEN);
+}
+
+static unsigned int __stdcall
+test_control (void * /* param */)
+{
+ // cd to ace dir?? (can this be an env variable?)
+
+ // redirect stdout/stderr to a file
+ logf = CreateFile (LogName,
+ FILE_ALL_ACCESS,
+ FILE_SHARE_READ,
+ 0, // security
+ OPEN_ALWAYS, // Don't crush a previous one
+ FILE_ATTRIBUTE_NORMAL,
+ 0);
+ if (logf == INVALID_HANDLE_VALUE)
+ perror (LogName);
+ else
+ {
+ SetFilePointer (logf, 0, 0, FILE_END); // Append new content
+ SetStdHandle (STD_OUTPUT_HANDLE, logf);
+ SetStdHandle (STD_ERROR_HANDLE, logf);
+ }
+
+ WORD want;
+ WSADATA offer;
+ want = MAKEWORD (2, 2);
+ if (0 != WSAStartup (want, &offer))
+ {
+ perror ("WSAStartup");
+ CloseHandle (logf);
+ return WSAGetLastError ();
+ }
+
+ // listen on port 8888 (can I set an env variable for this?)
+ SOCKET acceptor = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ sockaddr_in listen_addr;
+ memset (&listen_addr, 0, sizeof (listen_addr));
+ listen_addr.sin_family = AF_INET;
+ listen_addr.sin_addr.s_addr = INADDR_ANY;
+ listen_addr.sin_port = htons (8888);
+ if (SOCKET_ERROR == bind (acceptor,
+ (struct sockaddr *)&listen_addr,
+ sizeof (listen_addr)))
+ {
+ perror ("bind");
+ }
+ else
+ {
+ listen (acceptor, 10);
+ SOCKET peer;
+ while ((peer = accept (acceptor, 0, 0)) != INVALID_SOCKET)
+ {
+ Peer *p = new Peer (peer);
+ if (p == 0)
+ {
+ perror ("Out of memory");
+ closesocket (peer);
+ peer = INVALID_SOCKET;
+ continue;
+ }
+ if (0 == _beginthreadex (0, // security
+ 64 * 1024, // stack size
+ peer_svc, // entrypoint
+ (void *)p, // param
+ 0, // creation flags
+ 0)) // ptr to thread id
+ {
+ perror ("beginthreadex peer");
+ closesocket (peer);
+ delete p;
+ }
+ p = 0;
+ peer = INVALID_SOCKET;
+ }
+ perror ("accept");
+ }
+
+ closesocket (acceptor);
+ WSACleanup ();
+ return 0;
+}
+
+// Entrypoint for thread that's spawned to run a peer's session. Direct
+// control to the peer class.
+static unsigned int __stdcall
+peer_svc (void *peer_p)
+{
+ Peer *p = (Peer *)peer_p;
+ DWORD status = p->svc ();
+ delete p;
+ return status;
+}
+
+// Entrypoint for the thread spawned to run a test. The thread arg is the
+// Test * - call back to the test's run() method; return the test exit code
+// as the thread's return value.
+static unsigned int __stdcall
+run_test (void *test_p)
+{
+ Test *t = (Test *)test_p;
+ return t->run ();
+}
+
+// Format a Windows system or Winsock error message given an error code.
+static const char *
+format_errmsg (unsigned int errcode, const char *prefix)
+{
+ static const size_t errmsgsize = 1024;
+ static char errmsg[errmsgsize];
+
+ sprintf (errmsg, "%s: ", prefix);
+ size_t len = strlen (errmsg);
+ char *next = &errmsg[len];
+ size_t max_fmt = errmsgsize - len;
+ if (0 != FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ 0,
+ errcode,
+ 0, // Use default language
+ next,
+ (DWORD)max_fmt,
+ 0))
+ {
+ strcat (errmsg, "\n");
+ return errmsg;
+ }
+
+ errno = errcode;
+ char *msg = _strerror (prefix);
+ sprintf (errmsg, "err %d: %s", errcode, msg);
+ return errmsg;
+}
+
+#ifdef TEST_RUNNER_EXPORTS
+#define TEST_RUNNER_API __declspec(dllexport)
+#else
+#define TEST_RUNNER_API __declspec(dllimport)
+#endif
+
+__declspec(dllexport) int test_entry(void)
+{
+ return 0;
+}