1 |
#include "LSCPTest.h" |
2 |
|
3 |
#include "../common/global.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); |
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 |
162 |
string LSCPTest::receiveSingleLineAnswerFromLSCPServer() { |
163 |
string msg = receiveAnswerFromLSCPServer("\n"); |
164 |
// remove the line feed at the end |
165 |
static const string linedelimiter = "\r\n"; |
166 |
string::size_type pos = msg.rfind(linedelimiter); |
167 |
return msg.substr(0, pos); |
168 |
} |
169 |
|
170 |
/// wait until LSCP server answers with a multi line answer (throws LinuxSamplerException if optional timeout exceeded) |
171 |
vector<string> LSCPTest::receiveMultiLineAnswerFromLSCPServer(uint timeout_seconds) throw (LinuxSamplerException) { |
172 |
string msg = receiveAnswerFromLSCPServer("\n.\r\n", timeout_seconds); |
173 |
return __ConvertMultiLineMessage(msg); |
174 |
} |
175 |
|
176 |
/// wait until LSCP server answers with the given \a delimiter token at the end (throws LinuxSamplerException if optional timeout exceeded or socket error occured) |
177 |
string LSCPTest::receiveAnswerFromLSCPServer(string delimiter, uint timeout_seconds) throw (LinuxSamplerException) { |
178 |
string message; |
179 |
char c; |
180 |
fd_set sockSet; |
181 |
timeval timeout; |
182 |
|
183 |
while (true) { |
184 |
if (timeout_seconds) { |
185 |
FD_ZERO(&sockSet); |
186 |
FD_SET(hSocket, &sockSet); |
187 |
timeout.tv_sec = timeout_seconds; |
188 |
timeout.tv_usec = 0; |
189 |
int res = select(hSocket + 1, &sockSet, NULL, NULL, &timeout); |
190 |
if (!res) { // if timeout exceeded |
191 |
if (!message.size()) throw LinuxSamplerException("LSCPTest::receiveAnswerFromLSCPServer(): timeout (" + ToString(timeout_seconds) + "s) exceeded, no answer received"); |
192 |
else throw LinuxSamplerException("LSCPTest::receiveAnswerFromLSCPServer(): timeout (" + ToString(timeout_seconds) + "s) exceeded waiting for expected answer (end), received answer: \'" + message + "\'"); |
193 |
} |
194 |
else if (res < 0) { |
195 |
throw LinuxSamplerException("LSCPTest::receiveAnswerFromLSCPServer(): select error"); |
196 |
} |
197 |
} |
198 |
|
199 |
// there's something to read, so read one character |
200 |
int res = recv(hSocket, &c, 1, 0); |
201 |
if (!res) throw LinuxSamplerException("LSCPTest::receiveAnswerFromLSCPServer(): connection to LSCP server closed"); |
202 |
else if (res < 0) { |
203 |
switch(errno) { |
204 |
case EBADF: |
205 |
throw LinuxSamplerException("The argument s is an invalid descriptor"); |
206 |
case ECONNREFUSED: |
207 |
throw LinuxSamplerException("A remote host refused to allow the network connection (typically because it is not running the requested service)."); |
208 |
case ENOTCONN: |
209 |
throw LinuxSamplerException("The socket is associated with a connection-oriented protocol and has not been connected (see connect(2) and accept(2))."); |
210 |
case ENOTSOCK: |
211 |
throw LinuxSamplerException("The argument s does not refer to a socket."); |
212 |
case EAGAIN: |
213 |
continue; |
214 |
//throw LinuxSamplerException("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."); |
215 |
case EINTR: |
216 |
throw LinuxSamplerException("The receive was interrupted by delivery of a signal before any data were available."); |
217 |
case EFAULT: |
218 |
throw LinuxSamplerException("The receive buffer pointer(s) point outside the process's address space."); |
219 |
case EINVAL: |
220 |
throw LinuxSamplerException("Invalid argument passed."); |
221 |
case ENOMEM: |
222 |
throw LinuxSamplerException("Could not allocate memory for recvmsg."); |
223 |
default: |
224 |
throw LinuxSamplerException("Unknown recv() error."); |
225 |
} |
226 |
} |
227 |
|
228 |
message += c; |
229 |
string::size_type pos = message.rfind(delimiter); // ouch, but this is only a test case, right? ;) |
230 |
if (pos != string::npos) return message; |
231 |
} |
232 |
} |
233 |
|
234 |
|
235 |
|
236 |
void LSCPTest::printTestSuiteName() { |
237 |
cout << "\b \nRunning LSCP Tests: " << flush; |
238 |
} |
239 |
|
240 |
void LSCPTest::setUp() { |
241 |
} |
242 |
|
243 |
void LSCPTest::tearDown() { |
244 |
} |
245 |
|
246 |
|
247 |
|
248 |
// Check if we can launch the LSCP Server (implies that there's no other instance running at the moment). |
249 |
void LSCPTest::testLaunchLSCPServer() { |
250 |
//cout << "testLaunchLSCPServer()\n" << flush; |
251 |
CPPUNIT_ASSERT(launchLSCPServer()); |
252 |
} |
253 |
|
254 |
// Check if we can connect a client connection to the LSCP server and close that connection without problems. |
255 |
void LSCPTest::testConnectToLSCPServer() { |
256 |
//cout << "testConnectToLSCPServer()\n" << flush; |
257 |
sleep(1); // wait 1s |
258 |
CPPUNIT_ASSERT(connectToLSCPServer()); |
259 |
sleep(2); // wait 2s |
260 |
CPPUNIT_ASSERT(closeConnectionToLSCPServer()); |
261 |
} |
262 |
|
263 |
// Check "ADD CHANNEL" LSCP command. |
264 |
void LSCPTest::test_ADD_CHANNEL() { |
265 |
sleep(1); // wait 1s |
266 |
CPPUNIT_ASSERT(connectToLSCPServer()); |
267 |
|
268 |
sendCommandToLSCPServer("ADD CHANNEL"); |
269 |
CPPUNIT_ASSERT(receiveSingleLineAnswerFromLSCPServer() == "OK[0]"); |
270 |
|
271 |
sendCommandToLSCPServer("ADD CHANNEL"); |
272 |
CPPUNIT_ASSERT(receiveSingleLineAnswerFromLSCPServer() == "OK[1]"); |
273 |
|
274 |
sendCommandToLSCPServer("ADD CHANNEL"); |
275 |
CPPUNIT_ASSERT(receiveSingleLineAnswerFromLSCPServer() == "OK[2]"); |
276 |
} |
277 |
|
278 |
// Check "GET CHANNELS" LSCP command. |
279 |
void LSCPTest::test_GET_CHANNELS() { |
280 |
sendCommandToLSCPServer("GET CHANNELS"); |
281 |
string answer = receiveSingleLineAnswerFromLSCPServer(); |
282 |
int initial_channels = atoi(answer.c_str()); |
283 |
|
284 |
// add sampler channels and check if the count increases |
285 |
for (uint trial = 1; trial <= 3; trial++) { |
286 |
sendCommandToLSCPServer("ADD CHANNEL"); |
287 |
answer = receiveSingleLineAnswerFromLSCPServer(); |
288 |
sendCommandToLSCPServer("GET CHANNELS"); |
289 |
answer = receiveSingleLineAnswerFromLSCPServer(); |
290 |
int channels = atoi(answer.c_str()); |
291 |
CPPUNIT_ASSERT(channels == initial_channels + trial); |
292 |
} |
293 |
} |
294 |
|
295 |
// Check "REMOVE CHANNEL" LSCP command. |
296 |
void LSCPTest::test_REMOVE_CHANNEL() { |
297 |
// how many channels do we have at the moment? |
298 |
sendCommandToLSCPServer("GET CHANNELS"); |
299 |
string answer = receiveSingleLineAnswerFromLSCPServer(); |
300 |
int initial_channels = atoi(answer.c_str()); |
301 |
|
302 |
// if there are no sampler channels yet, create some |
303 |
if (!initial_channels) { |
304 |
const uint create_channels = 4; |
305 |
for (uint i = 0; i < create_channels; i++) { |
306 |
sendCommandToLSCPServer("ADD CHANNEL"); |
307 |
answer = receiveSingleLineAnswerFromLSCPServer(); |
308 |
} |
309 |
initial_channels = create_channels; |
310 |
} |
311 |
|
312 |
// now remove the channels until there is no one left and check if we really need 'initial_channels' times to achieve that |
313 |
for (uint channels = initial_channels; channels; channels--) { |
314 |
sendCommandToLSCPServer("LIST CHANNELS"); |
315 |
answer = receiveSingleLineAnswerFromLSCPServer(); |
316 |
if (answer == "") CPPUNIT_ASSERT(false); // no sampler channel left already? -> failure |
317 |
|
318 |
// take the last channel number in the list which we will take to remove that sampler channel |
319 |
string::size_type pos = answer.rfind(","); |
320 |
string channel_to_remove = (pos != string::npos) ? answer.substr(pos + 1, answer.length() - (pos + 1)) /* "m,n,...,t */ |
321 |
: answer; /* "k" */ |
322 |
|
323 |
//cout << " channel_to_remove: \"" << channel_to_remove << "\"\n" << flush; |
324 |
|
325 |
// remove that channel |
326 |
sendCommandToLSCPServer("REMOVE CHANNEL " + channel_to_remove); |
327 |
answer = receiveSingleLineAnswerFromLSCPServer(); |
328 |
CPPUNIT_ASSERT(answer == "OK"); |
329 |
} |
330 |
CPPUNIT_ASSERT(true); // success |
331 |
} |
332 |
|
333 |
// Check "GET AUDIO_OUTPUT_CHANNEL_PARAMETER INFO" LSCP command. |
334 |
void LSCPTest::test_GET_AUDIO_OUTPUT_CHANNEL_PARAMETER_INFO() { |
335 |
// first check if there's already an audio output device created |
336 |
sendCommandToLSCPServer("GET AUDIO_OUTPUT_DEVICES"); |
337 |
string answer = receiveSingleLineAnswerFromLSCPServer(); |
338 |
int devices = atoi(answer.c_str()); |
339 |
CPPUNIT_ASSERT(devices >= 0); |
340 |
if (!devices) { // if there's no audio output device yet, try to create one |
341 |
sendCommandToLSCPServer("GET AVAILABLE_AUDIO_OUTPUT_DRIVERS"); |
342 |
string drivers = receiveSingleLineAnswerFromLSCPServer(); |
343 |
CPPUNIT_ASSERT(drivers.size()); |
344 |
|
345 |
// iterate through all available drivers until device creation was successful |
346 |
do { |
347 |
optional<string> driver = __ExtractFirstToken(&drivers, ","); |
348 |
CPPUNIT_ASSERT(driver); |
349 |
|
350 |
sendCommandToLSCPServer("CREATE AUDIO_OUTPUT_DEVICE " + *driver); |
351 |
answer = receiveSingleLineAnswerFromLSCPServer(); |
352 |
} while (answer != "OK[0]"); |
353 |
} |
354 |
|
355 |
// now we can check the "GET AUDIO_OUTPUT_CHANNEL_PARAMETER INFO" command |
356 |
const uint timeout_seconds = 2; |
357 |
sendCommandToLSCPServer("GET AUDIO_OUTPUT_CHANNEL_PARAMETER INFO 0 0 NAME"); |
358 |
vector<string> vAnswer = receiveMultiLineAnswerFromLSCPServer(timeout_seconds); |
359 |
CPPUNIT_ASSERT(vAnswer.size() >= 4); // should at least contain tags TYPE, DESCRIPTION, FIX and MULTIPLICITY |
360 |
|
361 |
sendCommandToLSCPServer("GET AUDIO_OUTPUT_CHANNEL_PARAMETER INFO 0 0 IS_MIX_CHANNEL"); |
362 |
vAnswer = receiveMultiLineAnswerFromLSCPServer(timeout_seconds); |
363 |
CPPUNIT_ASSERT(vAnswer.size() >= 4); // should at least contain tags TYPE, DESCRIPTION, FIX and MULTIPLICITY |
364 |
} |
365 |
|
366 |
// Check if we can shutdown the LSCP Server without problems. |
367 |
void LSCPTest::testShutdownLSCPServer() { |
368 |
//cout << "testShutdownLSCPServer()\n" << flush; |
369 |
sleep(2); // wait 2s |
370 |
CPPUNIT_ASSERT(closeConnectionToLSCPServer()); |
371 |
sleep(3); // wait 3s |
372 |
CPPUNIT_ASSERT(shutdownLSCPServer()); |
373 |
} |