1 /******************************************************************************
2 
3   This source file is part of the MoleQueue project.
4 
5   Copyright 2012 Kitware, Inc.
6 
7   This source code is released under the New BSD License, (the "License").
8 
9   Unless required by applicable law or agreed to in writing, software
10   distributed under the License is distributed on an "AS IS" BASIS,
11   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   See the License for the specific language governing permissions and
13   limitations under the License.
14 
15 ******************************************************************************/
16 
17 #include <QtTest>
18 
19 #include "filesystemtools.h"
20 
21 #include "molequeuetestconfig.h"
22 #include "testserver.h" // for getRandomSocketName
23 
24 #include <QtCore/QProcess>
25 #include <QtCore/QProcessEnvironment>
26 
27 // Define ENABLE_ZMQ_TESTS if both zeromq and python are available
28 #ifdef MoleQueue_PYTHON_EXECUTABLE
29 #ifdef MoleQueue_HAS_ZMQ
30 #define ENABLE_ZMQ_TESTS
31 #endif // MoleQueue_HAS_ZMQ
32 #endif // MoleQueue_PYTHON_EXECUTABLE
33 
34 class ClientServerTest : public QObject
35 {
36   Q_OBJECT
37 public:
ClientServerTest()38   ClientServerTest()
39     : QObject(NULL),
40       m_numClients(10),
41       m_workDir(MoleQueue_BINARY_DIR "/testworkdir"),
42       m_moleQueueExecutable(MoleQueue_BINARY_DIR "/bin/molequeue"),
43       m_serverProcess(NULL)
44   {
45 #ifdef __APPLE__
46     m_moleQueueExecutable = MoleQueue_BINARY_DIR "/bin/molequeue.app/Contents/"
47         "MacOS/molequeue";
48 #endif // __APPLE__
49     randomizeSocketName();
50   }
51 
52 private:
53   int m_numClients;
54   QString m_workDir;
55   QString m_socketName;
56   QString m_moleQueueExecutable;
57   QStringList m_moleQueueDefaultArgs;
58   QProcess *m_serverProcess;
59   QList<QProcess*> m_clientProcesses;
60 
61   /// Delete the testing workdir and initialize it with the directory at
62   /// @a sourcePath.
63   bool resetWorkDir(const QString &sourcePath);
64 
65   /// Create a new randomized socket name, stored in m_socketName;
66   void randomizeSocketName();
67 
68   /// Create the server process (m_serverProcess) and reset
69   /// m_moleQueueDefaultArgs to set the workdir, socketname, and enable rpcKill.
70   bool setupServerProcess();
71 
72   /// Create a Cxx client process
73   QProcess *addClientProcess();
74 
75 #ifdef ENABLE_ZMQ_TESTS
76   /// Create a client process initialized for python. The process is returned
77   /// and added to m_clientProcesses.
78   QProcess *addPythonClientProcess();
79 #endif // ENABLE_ZMQ_TESTS
80 
81 private slots:
82   /// Called before the first test function is executed.
83   void initTestCase();
84   /// Called after the last test function is executed.
85   void cleanupTestCase();
86   /// Called before each test function is executed.
87   void init();
88   /// Called after every test function.
89   void cleanup();
90 
91   // Python client tests:
92 #ifdef ENABLE_ZMQ_TESTS
93   void submitOnePy();
94   void submit200Py();
95   void submit200FromManyClientsPy();
96 #endif // ENABLE_ZMQ_TESTS
97 };
98 
99 
resetWorkDir(const QString & sourcePath)100 bool ClientServerTest::resetWorkDir(const QString &sourcePath)
101 {
102   // Initialize working directory
103   if (QFileInfo(m_workDir).exists()) {
104     if (!MoleQueue::FileSystemTools::recursiveRemoveDirectory(m_workDir)) {
105       qWarning() << "Could not remove old working directory" << m_workDir;
106       return false;
107     }
108   }
109   if (!MoleQueue::FileSystemTools::recursiveCopyDirectory(sourcePath,
110                                                           m_workDir)) {
111     qWarning() << "Could not initialize working directory" << m_workDir
112                << "from" << sourcePath;
113     return false;
114   }
115   return true;
116 }
117 
randomizeSocketName()118 void ClientServerTest::randomizeSocketName()
119 {
120   m_socketName = TestServer::getRandomSocketName();
121 }
122 
setupServerProcess()123 bool ClientServerTest::setupServerProcess()
124 {
125   m_moleQueueDefaultArgs.clear();
126   m_moleQueueDefaultArgs
127       << "--workdir" << m_workDir
128       << "--socketname" << m_socketName
129       << "--rpc-kill";
130 
131   if (m_serverProcess) {
132     delete m_serverProcess;
133     m_serverProcess = NULL;
134   }
135   m_serverProcess = new QProcess(this);
136   m_serverProcess->setProcessChannelMode(QProcess::ForwardedChannels);
137   return true;
138 }
139 
addClientProcess()140 QProcess *ClientServerTest::addClientProcess()
141 {
142   QProcess *clientProcess = new QProcess(this);
143   clientProcess->setProcessChannelMode(::QProcess::ForwardedChannels);
144   m_clientProcesses.append(clientProcess);
145   return clientProcess;
146 }
147 
148 #ifdef ENABLE_ZMQ_TESTS
addPythonClientProcess()149 QProcess *ClientServerTest::addPythonClientProcess()
150 {
151   QProcess *clientProcess = addClientProcess();
152   QProcessEnvironment env = clientProcess->processEnvironment();
153   env.insert("PYTHONPATH", (env.value("PYTHONPATH").isEmpty()
154                             ? QString()
155                             : env.value("PYTHONPATH") + ':') +
156              MoleQueue_SOURCE_DIR "/python");
157   clientProcess->setProcessEnvironment(env);
158   return clientProcess;
159 }
160 #endif // ENABLE_ZMQ_TESTS
161 
initTestCase()162 void ClientServerTest::initTestCase()
163 {
164   QVERIFY2(resetWorkDir(MoleQueue_TESTDATA_DIR "/testworkdir_unix"),
165            "Failed to reset working directory for test.");
166 
167   // Setup server process
168   QVERIFY(setupServerProcess());
169 
170   // Start server
171   qDebug() << "Starting server:" << m_moleQueueExecutable
172            << m_moleQueueDefaultArgs.join(" ");
173   m_serverProcess->start(m_moleQueueExecutable, m_moleQueueDefaultArgs);
174   QVERIFY(m_serverProcess->waitForStarted(10*1000));
175   QTest::qSleep(1 * 1000); // Wait one second for server to start
176 }
177 
cleanupTestCase()178 void ClientServerTest::cleanupTestCase()
179 {
180   // send killRpc message
181   QProcess *clientProcess = addClientProcess();
182   QString clientCommand = MoleQueue_TESTEXEC_DIR "sendRpcKill";
183   QStringList clientArguments;
184   clientArguments << "-s" << m_socketName;
185 
186   qDebug() << "Starting client:" << clientCommand
187            << clientArguments.join(" ");
188   clientProcess->start(clientCommand, clientArguments);
189 
190   // Wait for client to finish
191   QVERIFY2(clientProcess->waitForFinished(300*1000), "Client timed out.");
192   QCOMPARE(clientProcess->exitCode(), 0);
193 
194   // Wait for server to finish
195   QVERIFY2(m_serverProcess->waitForFinished(5*1000), "Server timed out.");
196   QCOMPARE(m_serverProcess->exitCode(), 0);
197 
198   // In case the rpcKill call fails, kill the process
199   if (m_serverProcess->state() != QProcess::NotRunning)
200     m_serverProcess->kill();
201   m_serverProcess->deleteLater();
202   m_serverProcess = NULL;
203 
204   // Clean up the sendRpcKill client.
205   cleanup();
206 }
207 
init()208 void ClientServerTest::init()
209 {
210 }
211 
cleanup()212 void ClientServerTest::cleanup()
213 {
214   foreach (QProcess *proc, m_clientProcesses) {
215     if (proc->state() != QProcess::NotRunning)
216       proc->kill();
217   }
218   qDeleteAll(m_clientProcesses);
219   m_clientProcesses.clear();
220 }
221 
222 #ifdef ENABLE_ZMQ_TESTS
submitOnePy()223 void ClientServerTest::submitOnePy()
224 {
225   // Setup client process
226   QProcess *clientProcess = addPythonClientProcess();
227   QString clientCommand = MoleQueue_PYTHON_EXECUTABLE;
228   QStringList clientArguments;
229   clientArguments
230       << MoleQueue_TESTSCRIPT_DIR "/submitJob.py"
231       << "-s" << m_socketName
232       << "-n" << QString::number(1);
233 
234   qDebug() << "Starting client:" << clientCommand
235            << clientArguments.join(" ");
236 
237   clientProcess->start(clientCommand, QStringList(clientArguments));
238 
239   // Wait 5 seconds for client to start
240   QVERIFY2(clientProcess->waitForStarted(5*1000), "Client did not start.");
241 
242   // Wait 10 seconds for client to finish
243   QVERIFY2(clientProcess->waitForFinished(10*1000), "Client timed out.");
244   QCOMPARE(clientProcess->exitCode(), 0);
245 }
246 
submit200Py()247 void ClientServerTest::submit200Py()
248 {
249   // Setup client process
250   QProcess *clientProcess = addPythonClientProcess();
251   QString clientCommand = MoleQueue_PYTHON_EXECUTABLE;
252   QStringList clientArguments;
253   clientArguments
254       << MoleQueue_TESTSCRIPT_DIR "/submitJob.py"
255       << "-s" << m_socketName
256       << "-n" << QString::number(200);
257 
258   qDebug() << "Starting client:" << clientCommand
259            << clientArguments.join(" ");
260 
261   clientProcess->start(clientCommand, QStringList(clientArguments));
262 
263   // Wait 5 seconds for client to start
264   QVERIFY2(clientProcess->waitForStarted(5*1000), "Client did not start.");
265 
266   // Wait one minute for client to finish
267   QVERIFY2(clientProcess->waitForFinished(60*1000), "Client timed out.");
268   QCOMPARE(clientProcess->exitCode(), 0);
269 }
270 
submit200FromManyClientsPy()271 void ClientServerTest::submit200FromManyClientsPy()
272 {
273   // Setup client processes
274   while (m_clientProcesses.size() < m_numClients)
275     addPythonClientProcess();
276   QString clientCommand = MoleQueue_PYTHON_EXECUTABLE;
277   QStringList clientArguments;
278   clientArguments
279       << MoleQueue_TESTSCRIPT_DIR "/submitJob.py"
280       << "-s" << m_socketName
281       << "-n" << QString::number(200);
282 
283   qDebug() << "Starting" << m_numClients << "clients:" << clientCommand
284            << clientArguments.join(" ");
285 
286   int clientId = 0;
287   foreach (QProcess *cliProc, m_clientProcesses) {
288     cliProc->start(clientCommand,
289                    QStringList(clientArguments)
290                    << "-c" << QString::number(++clientId));
291   }
292 
293   // Wait 5 seconds for each client to start
294   clientId = 0;
295   foreach (QProcess *cliProc, m_clientProcesses) {
296     ++clientId;
297     QVERIFY2(cliProc->waitForStarted(5*1000),
298              (QByteArray("Client ") + QByteArray::number(clientId) +
299               QByteArray(" failed to start.")).constData());
300   }
301 
302   // Wait two minutes for all clients to finish
303   clientId = 0;
304   foreach (QProcess *cliProc, m_clientProcesses) {
305     ++clientId;
306     QVERIFY2(cliProc->waitForFinished(2*60*1000),
307              (QByteArray("Client ") + QByteArray::number(clientId) +
308               QByteArray(" timed out.")).constData());
309     QCOMPARE(cliProc->exitCode(), 0);
310   }
311 }
312 #endif // ENABLE_ZMQ_TESTS
313 QTEST_MAIN(ClientServerTest)
314 
315 #include "clientservertest.moc"
316