1 |
#include "LSCPTest.h" |
2 |
|
3 |
#include "../common/global_private.h" |
4 |
#include "../common/optional.h" |
5 |
|
6 |
#include <iostream> |
7 |
#include <stdio.h> |
8 |
#include <stdlib.h> |
9 |
#include <unistd.h> |
10 |
#include <fcntl.h> |
11 |
#include <sys/types.h> |
12 |
#include <sys/socket.h> |
13 |
|
14 |
CPPUNIT_TEST_SUITE_REGISTRATION(LSCPTest); |
15 |
|
16 |
// Note: |
17 |
// we have to declare all those variables which we want to use for all |
18 |
// tests within this test suite static because there are side effects which |
19 |
// occur on transition to the next test which would change the values of our |
20 |
// variables |
21 |
static Sampler* pSampler = NULL; |
22 |
static LSCPServer* pLSCPServer = NULL; |
23 |
static int hSocket = -1; |
24 |
|
25 |
/// Returns first token from \a sentence and removes that token (and evtl. delimiter) from \a sentence. |
26 |
static optional<string> __ExtractFirstToken(string* pSentence, const string Delimiter) { |
27 |
if (*pSentence == "") return optional<string>::nothing; |
28 |
|
29 |
string::size_type pos = pSentence->find(Delimiter); |
30 |
|
31 |
// if sentence has only one token |
32 |
if (pos == string::npos) { |
33 |
string token = *pSentence; |
34 |
*pSentence = ""; |
35 |
return token; |
36 |
} |
37 |
|
38 |
// sentence has more than one token, so extract the first token |
39 |
string token = pSentence->substr(0, pos); |
40 |
*pSentence = pSentence->replace(0, pos + 1, ""); |
41 |
return token; |
42 |
} |
43 |
|
44 |
// split the multi line response string into the individual lines and remove the last (delimiter) line and the line feed characters in all lines |
45 |
static vector<string> __ConvertMultiLineMessage(string msg) { |
46 |
vector<string> res; |
47 |
|
48 |
// erase the (dot) delimiter line |
49 |
static const string dotlinedelimiter = ".\r\n"; |
50 |
string::size_type pos = msg.rfind(dotlinedelimiter); |
51 |
msg = msg.replace(pos, dotlinedelimiter.length(), ""); |
52 |
|
53 |
// now split the lines |
54 |
static const string linedelimiter = "\r\n"; |
55 |
while (true) { |
56 |
pos = msg.find(linedelimiter, 0); |
57 |
|
58 |
if (pos == string::npos) break; // if we're done |
59 |
|
60 |
// get the line without the line feed and put it at the end of the vector |
61 |
string line = msg.substr(0, pos); |
62 |
res.push_back(line); |
63 |
|
64 |
// remove the line from the input string |
65 |
pos += linedelimiter.length(); |
66 |
msg = msg.substr(pos, msg.length() - pos); |
67 |
} |
68 |
|
69 |
return res; |
70 |
} |
71 |
|
72 |
|
73 |
// LSCPTest |
74 |
|
75 |
// returns false if the server could not be launched |
76 |
bool LSCPTest::launchLSCPServer() { |
77 |
const long timeout_seconds = 10; // we give the server max. 10 seconds to startup, otherwise we declare the startup as failed |
78 |
try { |
79 |
pSampler = new Sampler; |
80 |
pLSCPServer = new LSCPServer(pSampler, htonl(LSCP_ADDR), htons(LSCP_PORT)); |
81 |
pLSCPServer->StartThread(); |
82 |
int res = pLSCPServer->WaitUntilInitialized(timeout_seconds); |
83 |
if (res < 0) throw; |
84 |
|
85 |
return true; // success |
86 |
} |
87 |
catch (...) { |
88 |
pSampler = NULL; |
89 |
pLSCPServer = NULL; |
90 |
return false; // failure |
91 |
} |
92 |
} |
93 |
|
94 |
// returns false if the server could not be destroyed without problems |
95 |
bool LSCPTest::shutdownLSCPServer() { |
96 |
try { |
97 |
pLSCPServer->StopThread(); |
98 |
if (pLSCPServer) { |
99 |
delete pLSCPServer; |
100 |
pLSCPServer = NULL; |
101 |
} |
102 |
if (pSampler) { |
103 |
delete pSampler; |
104 |
pSampler = NULL; |
105 |
} |
106 |
return true; // success |
107 |
} |
108 |
catch (...) { |
109 |
return false; // failure |
110 |
} |
111 |
} |
112 |
|
113 |
// returns false if client connection to the LSCP server could not be established |
114 |
bool LSCPTest::connectToLSCPServer() { |
115 |
const int iPort = LSCP_PORT; // LSCP server listening port (from lscpserver.h) |
116 |
hSocket = -1; |
117 |
|
118 |
hostent* pHost = gethostbyname("localhost"); |
119 |
if (pHost == NULL) return false; |
120 |
|
121 |
hSocket = socket(AF_INET, SOCK_STREAM, 0); |
122 |
if (hSocket < 0) return false; |
123 |
|
124 |
sockaddr_in addr; |
125 |
memset((char*) &addr, 0, sizeof(sockaddr_in)); |
126 |
addr.sin_family = pHost->h_addrtype; |
127 |
memmove((char*) &(addr.sin_addr), pHost->h_addr, pHost->h_length); |
128 |
addr.sin_port = htons((short) iPort); |
129 |
|
130 |
if (connect(hSocket, (sockaddr*) &addr, sizeof(sockaddr_in)) < 0) { |
131 |
close(hSocket); |
132 |
return false; |
133 |
} |
134 |
|
135 |
// set non-blocking mode |
136 |
int socket_flags = fcntl(hSocket, F_GETFL, 0); |
137 |
fcntl(hSocket, F_SETFL, socket_flags | O_NONBLOCK); |
138 |
|
139 |
return true; |
140 |
} |
141 |
|
142 |
bool LSCPTest::closeConnectionToLSCPServer() { |
143 |
//cout << "closeConnectionToLSCPServer()\n" << flush; |
144 |
if (hSocket >= 0) { |
145 |
close(hSocket); |
146 |
hSocket = -1; |
147 |
} |
148 |
return true; |
149 |
} |
150 |
|
151 |
// send a command to the LSCP server |
152 |
void LSCPTest::sendCommandToLSCPServer(string cmd) { |
153 |
if (hSocket < 0) { |
154 |
cout << "sendCommandToLSCPServer() error: client socket not ready\n" << flush; |
155 |
return; |
156 |
} |
157 |
cmd += "\r\n"; |
158 |
send(hSocket, cmd.c_str(), cmd.length(), 0); |
159 |
} |
160 |
|
161 |
// wait until LSCP server answers with a single line answer (throws LinuxSampler::Exception if optional timeout exceeded) |
162 |
string LSCPTest::receiveSingleLineAnswerFromLSCPServer(uint timeout_seconds) throw (Exception) { |
163 |
string msg = receiveAnswerFromLSCPServer("\n", timeout_seconds); |
164 |
// remove carriage return characters |
165 |
string::size_type p = msg.find('\r'); |
166 |
for (; p != string::npos; p = msg.find(p, '\r')) msg.erase(p, 1); |
167 |
// remove the line feed at the end |
168 |
static const string linedelimiter = "\n"; |
169 |
string::size_type pos = msg.rfind(linedelimiter); |
170 |
return msg.substr(0, pos); |
171 |
} |
172 |
|
173 |
/// wait until LSCP server answers with a multi line answer (throws LinuxSampler::Exception if optional timeout exceeded) |
174 |
vector<string> LSCPTest::receiveMultiLineAnswerFromLSCPServer(uint timeout_seconds) throw (Exception) { |
175 |
string msg = receiveAnswerFromLSCPServer("\n.\r\n", timeout_seconds); |
176 |
return __ConvertMultiLineMessage(msg); |
177 |
} |
178 |
|
179 |
void LSCPTest::clearInputBuffer() { |
180 |
char c; |
181 |
while (recv(hSocket, &c, 1, 0) > 0); |
182 |
} |
183 |
|
184 |
/// wait until LSCP server answers with the given \a delimiter token at the end (throws LinuxSampler::Exception if optional timeout exceeded or socket error occured) |
185 |
string LSCPTest::receiveAnswerFromLSCPServer(string delimiter, uint timeout_seconds) throw (Exception) { |
186 |
string message; |
187 |
char c; |
188 |
fd_set sockSet; |
189 |
timeval timeout; |
190 |
|
191 |
while (true) { |
192 |
if (timeout_seconds) { |
193 |
FD_ZERO(&sockSet); |
194 |
FD_SET(hSocket, &sockSet); |
195 |
timeout.tv_sec = timeout_seconds; |
196 |
timeout.tv_usec = 0; |
197 |
int res = select(hSocket + 1, &sockSet, NULL, NULL, &timeout); |
198 |
if (!res) { // if timeout exceeded |
199 |
if (!message.size()) throw Exception("LSCPTest::receiveAnswerFromLSCPServer(): timeout (" + ToString(timeout_seconds) + "s) exceeded, no answer received"); |
200 |
else throw Exception("LSCPTest::receiveAnswerFromLSCPServer(): timeout (" + ToString(timeout_seconds) + "s) exceeded waiting for expected answer (end), received answer: \'" + message + "\'"); |
201 |
} |
202 |
else if (res < 0) { |
203 |
throw Exception("LSCPTest::receiveAnswerFromLSCPServer(): select error"); |
204 |
} |
205 |
} |
206 |
|
207 |
// there's something to read, so read one character |
208 |
int res = recv(hSocket, &c, 1, 0); |
209 |
if (!res) throw Exception("LSCPTest::receiveAnswerFromLSCPServer(): connection to LSCP server closed"); |
210 |
else if (res < 0) { |
211 |
switch(errno) { |
212 |
case EBADF: |
213 |
throw Exception("The argument s is an invalid descriptor"); |
214 |
case ECONNREFUSED: |
215 |
throw Exception("A remote host refused to allow the network connection (typically because it is not running the requested service)."); |
216 |
case ENOTCONN: |
217 |
throw Exception("The socket is associated with a connection-oriented protocol and has not been connected (see connect(2) and accept(2))."); |
218 |
case ENOTSOCK: |
219 |
throw Exception("The argument s does not refer to a socket."); |
220 |
case EAGAIN: |
221 |
continue; |
222 |
//throw Exception("The socket is marked non-blocking and the receive operation would block, or a receive timeout had been set and the timeout expired before data was received."); |
223 |
case EINTR: |
224 |
throw Exception("The receive was interrupted by delivery of a signal before any data were available."); |
225 |
case EFAULT: |
226 |
throw Exception("The receive buffer pointer(s) point outside the process's address space."); |
227 |
case EINVAL: |
228 |
throw Exception("Invalid argument passed."); |
229 |
case ENOMEM: |
230 |
throw Exception("Could not allocate memory for recvmsg."); |
231 |
default: |
232 |
throw Exception("Unknown recv() error."); |
233 |
} |
234 |
} |
235 |
|
236 |
message += c; |
237 |
string::size_type pos = message.rfind(delimiter); // ouch, but this is only a test case, right? ;) |
238 |
if (pos != string::npos) return message; |
239 |
} |
240 |
} |
241 |
|
242 |
|
243 |
|
244 |
void LSCPTest::printTestSuiteName() { |
245 |
cout << "\b \nRunning LSCP Tests: " << flush; |
246 |
} |
247 |
|
248 |
void LSCPTest::setUp() { |
249 |
} |
250 |
|
251 |
void LSCPTest::tearDown() { |
252 |
clearInputBuffer(); // to avoid that the next test reads an answer from a previous test |
253 |
} |
254 |
|
255 |
|
256 |
|
257 |
// Check if we can launch the LSCP Server (implies that there's no other instance running at the moment). |
258 |
void LSCPTest::testLaunchLSCPServer() { |
259 |
//cout << "testLaunchLSCPServer()\n" << flush; |
260 |
CPPUNIT_ASSERT(launchLSCPServer()); |
261 |
} |
262 |
|
263 |
// Check if we can connect a client connection to the LSCP server and close that connection without problems. |
264 |
void LSCPTest::testConnectToLSCPServer() { |
265 |
//cout << "testConnectToLSCPServer()\n" << flush; |
266 |
sleep(1); // wait 1s |
267 |
CPPUNIT_ASSERT(connectToLSCPServer()); |
268 |
sleep(2); // wait 2s |
269 |
CPPUNIT_ASSERT(closeConnectionToLSCPServer()); |
270 |
} |
271 |
|
272 |
// Check "ADD CHANNEL" LSCP command. |
273 |
void LSCPTest::test_ADD_CHANNEL() { |
274 |
sleep(1); // wait 1s |
275 |
CPPUNIT_ASSERT(connectToLSCPServer()); |
276 |
|
277 |
sendCommandToLSCPServer("ADD CHANNEL"); |
278 |
CPPUNIT_ASSERT(receiveSingleLineAnswerFromLSCPServer() == "OK[0]"); |
279 |
|
280 |
sendCommandToLSCPServer("ADD CHANNEL"); |
281 |
CPPUNIT_ASSERT(receiveSingleLineAnswerFromLSCPServer() == "OK[1]"); |
282 |
|
283 |
sendCommandToLSCPServer("ADD CHANNEL"); |
284 |
CPPUNIT_ASSERT(receiveSingleLineAnswerFromLSCPServer() == "OK[2]"); |
285 |
} |
286 |
|
287 |
// Check "GET CHANNELS" LSCP command. |
288 |
void LSCPTest::test_GET_CHANNELS() { |
289 |
sendCommandToLSCPServer("GET CHANNELS"); |
290 |
string answer = receiveSingleLineAnswerFromLSCPServer(); |
291 |
int initial_channels = atoi(answer.c_str()); |
292 |
|
293 |
// add sampler channels and check if the count increases |
294 |
for (uint trial = 1; trial <= 3; trial++) { |
295 |
sendCommandToLSCPServer("ADD CHANNEL"); |
296 |
answer = receiveSingleLineAnswerFromLSCPServer(); |
297 |
sendCommandToLSCPServer("GET CHANNELS"); |
298 |
answer = receiveSingleLineAnswerFromLSCPServer(); |
299 |
int channels = atoi(answer.c_str()); |
300 |
CPPUNIT_ASSERT(channels == initial_channels + trial); |
301 |
} |
302 |
} |
303 |
|
304 |
// Check "REMOVE CHANNEL" LSCP command. |
305 |
void LSCPTest::test_REMOVE_CHANNEL() { |
306 |
// how many channels do we have at the moment? |
307 |
sendCommandToLSCPServer("GET CHANNELS"); |
308 |
string answer = receiveSingleLineAnswerFromLSCPServer(); |
309 |
int initial_channels = atoi(answer.c_str()); |
310 |
|
311 |
// if there are no sampler channels yet, create some |
312 |
if (!initial_channels) { |
313 |
const uint create_channels = 4; |
314 |
for (uint i = 0; i < create_channels; i++) { |
315 |
sendCommandToLSCPServer("ADD CHANNEL"); |
316 |
answer = receiveSingleLineAnswerFromLSCPServer(); |
317 |
} |
318 |
initial_channels = create_channels; |
319 |
} |
320 |
|
321 |
// now remove the channels until there is no one left and check if we really need 'initial_channels' times to achieve that |
322 |
for (uint channels = initial_channels; channels; channels--) { |
323 |
sendCommandToLSCPServer("LIST CHANNELS"); |
324 |
answer = receiveSingleLineAnswerFromLSCPServer(); |
325 |
if (answer == "") CPPUNIT_ASSERT(false); // no sampler channel left already? -> failure |
326 |
|
327 |
// take the last channel number in the list which we will take to remove that sampler channel |
328 |
string::size_type pos = answer.rfind(","); |
329 |
string channel_to_remove = (pos != string::npos) ? answer.substr(pos + 1, answer.length() - (pos + 1)) /* "m,n,...,t */ |
330 |
: answer; /* "k" */ |
331 |
|
332 |
//cout << " channel_to_remove: \"" << channel_to_remove << "\"\n" << flush; |
333 |
|
334 |
// remove that channel |
335 |
sendCommandToLSCPServer("REMOVE CHANNEL " + channel_to_remove); |
336 |
answer = receiveSingleLineAnswerFromLSCPServer(); |
337 |
CPPUNIT_ASSERT(answer == "OK"); |
338 |
} |
339 |
CPPUNIT_ASSERT(true); // success |
340 |
} |
341 |
|
342 |
// Check "GET AUDIO_OUTPUT_CHANNEL_PARAMETER INFO" LSCP command. |
343 |
void LSCPTest::test_GET_AUDIO_OUTPUT_CHANNEL_PARAMETER_INFO() { |
344 |
// first check if there's already an audio output device created |
345 |
sendCommandToLSCPServer("GET AUDIO_OUTPUT_DEVICES"); |
346 |
string answer = receiveSingleLineAnswerFromLSCPServer(); |
347 |
int devices = atoi(answer.c_str()); |
348 |
CPPUNIT_ASSERT(devices >= 0); |
349 |
if (!devices) { // if there's no audio output device yet, try to create one |
350 |
sendCommandToLSCPServer("LIST AVAILABLE_AUDIO_OUTPUT_DRIVERS"); |
351 |
string drivers = receiveSingleLineAnswerFromLSCPServer(); |
352 |
CPPUNIT_ASSERT(drivers.size()); |
353 |
|
354 |
// iterate through all available drivers until device creation was successful |
355 |
do { |
356 |
optional<string> driver = __ExtractFirstToken(&drivers, ","); |
357 |
CPPUNIT_ASSERT(driver); |
358 |
|
359 |
sendCommandToLSCPServer("CREATE AUDIO_OUTPUT_DEVICE " + *driver); |
360 |
answer = receiveSingleLineAnswerFromLSCPServer(120); // wait 2 minutes for an answer |
361 |
} while (answer != "OK[0]"); |
362 |
} |
363 |
|
364 |
// now we can check the "GET AUDIO_OUTPUT_CHANNEL_PARAMETER INFO" command |
365 |
const uint timeout_seconds = 2; |
366 |
sendCommandToLSCPServer("GET AUDIO_OUTPUT_CHANNEL_PARAMETER INFO 0 0 NAME"); |
367 |
vector<string> vAnswer = receiveMultiLineAnswerFromLSCPServer(timeout_seconds); |
368 |
CPPUNIT_ASSERT(vAnswer.size() >= 4); // should at least contain tags TYPE, DESCRIPTION, FIX and MULTIPLICITY |
369 |
|
370 |
sendCommandToLSCPServer("GET AUDIO_OUTPUT_CHANNEL_PARAMETER INFO 0 0 IS_MIX_CHANNEL"); |
371 |
vAnswer = receiveMultiLineAnswerFromLSCPServer(timeout_seconds); |
372 |
CPPUNIT_ASSERT(vAnswer.size() >= 4); // should at least contain tags TYPE, DESCRIPTION, FIX and MULTIPLICITY |
373 |
} |
374 |
|
375 |
// Check "SET ECHO" LSCP command. |
376 |
void LSCPTest::test_SET_ECHO() { |
377 |
// enable echo mode |
378 |
sendCommandToLSCPServer("SET ECHO 1"); |
379 |
CPPUNIT_ASSERT(receiveSingleLineAnswerFromLSCPServer() == "OK"); |
380 |
|
381 |
// check if commands will actually be echoed now |
382 |
sendCommandToLSCPServer("GET CHANNELS"); // send an arbitrary command |
383 |
CPPUNIT_ASSERT(receiveSingleLineAnswerFromLSCPServer(2) == "GET CHANNELS"); |
384 |
receiveSingleLineAnswerFromLSCPServer(2); // throws exception if no answer received after 2s (usually we expect the answer from our command here) |
385 |
|
386 |
// disable echo mode |
387 |
sendCommandToLSCPServer("SET ECHO 0"); |
388 |
CPPUNIT_ASSERT(receiveSingleLineAnswerFromLSCPServer() == "SET ECHO 0"); // this will be echoed though |
389 |
CPPUNIT_ASSERT(receiveSingleLineAnswerFromLSCPServer() == "OK"); |
390 |
|
391 |
// check if commands will not be echoed now |
392 |
sendCommandToLSCPServer("GET CHANNELS"); |
393 |
CPPUNIT_ASSERT(receiveSingleLineAnswerFromLSCPServer() != "GET CHANNELS"); |
394 |
} |
395 |
|
396 |
// Check if we can shutdown the LSCP Server without problems. |
397 |
void LSCPTest::testShutdownLSCPServer() { |
398 |
//cout << "testShutdownLSCPServer()\n" << flush; |
399 |
sleep(2); // wait 2s |
400 |
CPPUNIT_ASSERT(closeConnectionToLSCPServer()); |
401 |
sleep(3); // wait 3s |
402 |
CPPUNIT_ASSERT(shutdownLSCPServer()); |
403 |
} |