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 |
|
11 |
CPPUNIT_TEST_SUITE_REGISTRATION(LSCPTest); |
12 |
|
13 |
// Note: |
14 |
// we have to declare all those variables which we want to use for all |
15 |
// tests within this test suite static because there are side effects which |
16 |
// occur on transition to the next test which would change the values of our |
17 |
// variables |
18 |
static Sampler* pSampler = NULL; |
19 |
static LSCPServer* pLSCPServer = NULL; |
20 |
static int hSocket = -1; |
21 |
static FILE* hServerIn = NULL; |
22 |
|
23 |
/// Returns first token from \a sentence and removes that token (and evtl. delimiter) from \a sentence. |
24 |
static optional<string> __ExtractFirstToken(string* pSentence, const string Delimiter) { |
25 |
if (*pSentence == "") return optional<string>::nothing; |
26 |
|
27 |
string::size_type pos = pSentence->find(Delimiter); |
28 |
|
29 |
// if sentence has only one token |
30 |
if (pos == string::npos) { |
31 |
string token = *pSentence; |
32 |
*pSentence = ""; |
33 |
return token; |
34 |
} |
35 |
|
36 |
// sentence has more than one token, so extract the first token |
37 |
string token = pSentence->substr(0, pos); |
38 |
*pSentence = pSentence->replace(0, pos + 1, ""); |
39 |
return token; |
40 |
} |
41 |
|
42 |
// split the multi line response string into the individual lines and remove the last (delimiter) line and the line feed characters in all lines |
43 |
static vector<string> __ConvertMultiLineMessage(string msg) { |
44 |
vector<string> res; |
45 |
|
46 |
// erase the (dot) delimiter line |
47 |
static const string dotlinedelimiter = ".\r\n"; |
48 |
string::size_type pos = msg.rfind(dotlinedelimiter); |
49 |
msg = msg.replace(pos, dotlinedelimiter.length(), ""); |
50 |
|
51 |
// now split the lines |
52 |
static const string linedelimiter = "\r\n"; |
53 |
while (true) { |
54 |
pos = msg.find(linedelimiter, 0); |
55 |
|
56 |
if (pos == string::npos) break; // if we're done |
57 |
|
58 |
// get the line without the line feed and put it at the end of the vector |
59 |
string line = msg.substr(0, pos); |
60 |
res.push_back(line); |
61 |
|
62 |
// remove the line from the input string |
63 |
pos += linedelimiter.length(); |
64 |
msg = msg.substr(pos, msg.length() - pos); |
65 |
} |
66 |
|
67 |
return res; |
68 |
} |
69 |
|
70 |
|
71 |
// LSCPTest |
72 |
|
73 |
// returns false if the server could not be launched |
74 |
bool LSCPTest::launchLSCPServer() { |
75 |
const long timeout_seconds = 10; // we give the server max. 10 seconds to startup, otherwise we declare the startup as failed |
76 |
try { |
77 |
pSampler = new Sampler; |
78 |
pLSCPServer = new LSCPServer(pSampler); |
79 |
pLSCPServer->StartThread(); |
80 |
int res = pLSCPServer->WaitUntilInitialized(timeout_seconds); |
81 |
if (res < 0) throw; |
82 |
|
83 |
return true; // success |
84 |
} |
85 |
catch (...) { |
86 |
pSampler = NULL; |
87 |
pLSCPServer = NULL; |
88 |
return false; // failure |
89 |
} |
90 |
} |
91 |
|
92 |
// returns false if the server could not be destroyed without problems |
93 |
bool LSCPTest::shutdownLSCPServer() { |
94 |
try { |
95 |
pLSCPServer->StopThread(); |
96 |
if (pLSCPServer) { |
97 |
delete pLSCPServer; |
98 |
pLSCPServer = NULL; |
99 |
} |
100 |
if (pSampler) { |
101 |
delete pSampler; |
102 |
pSampler = NULL; |
103 |
} |
104 |
return true; // success |
105 |
} |
106 |
catch (...) { |
107 |
return false; // failure |
108 |
} |
109 |
} |
110 |
|
111 |
// returns false if client connection to the LSCP server could not be established |
112 |
bool LSCPTest::connectToLSCPServer() { |
113 |
const int iPort = LSCP_PORT; // LSCP server listening port (from lscpserver.h) |
114 |
hSocket = -1; |
115 |
|
116 |
hostent* pHost = gethostbyname("localhost"); |
117 |
if (pHost == NULL) return false; |
118 |
|
119 |
hSocket = socket(AF_INET, SOCK_STREAM, 0); |
120 |
if (hSocket < 0) return false; |
121 |
|
122 |
sockaddr_in addr; |
123 |
memset((char*) &addr, 0, sizeof(sockaddr_in)); |
124 |
addr.sin_family = pHost->h_addrtype; |
125 |
memmove((char*) &(addr.sin_addr), pHost->h_addr, pHost->h_length); |
126 |
addr.sin_port = htons((short) iPort); |
127 |
|
128 |
if (connect(hSocket, (sockaddr*) &addr, sizeof(sockaddr_in)) < 0) { |
129 |
close(hSocket); |
130 |
return false; |
131 |
} |
132 |
|
133 |
hServerIn = fdopen(hSocket, "r"); |
134 |
|
135 |
return true; |
136 |
} |
137 |
|
138 |
bool LSCPTest::closeConnectionToLSCPServer() { |
139 |
//cout << "closeConnectionToLSCPServer()\n" << flush; |
140 |
hServerIn = NULL; |
141 |
if (hSocket >= 0) { |
142 |
close(hSocket); |
143 |
hSocket = -1; |
144 |
} |
145 |
return true; |
146 |
} |
147 |
|
148 |
// send a command to the LSCP server |
149 |
void LSCPTest::sendCommandToLSCPServer(string cmd) { |
150 |
if (hSocket < 0) { |
151 |
cout << "sendCommandToLSCPServer() error: client socket not ready\n" << flush; |
152 |
return; |
153 |
} |
154 |
cmd += "\r\n"; |
155 |
send(hSocket, cmd.c_str(), cmd.length(), 0); |
156 |
} |
157 |
|
158 |
// wait until LSCP server answers with a single line answer |
159 |
string LSCPTest::receiveSingleLineAnswerFromLSCPServer() { |
160 |
string msg = receiveAnswerFromLSCPServer("\n"); |
161 |
// remove the line feed at the end |
162 |
static const string linedelimiter = "\r\n"; |
163 |
string::size_type pos = msg.rfind(linedelimiter); |
164 |
return msg.substr(0, pos); |
165 |
} |
166 |
|
167 |
/// wait until LSCP server answers with a multi line answer (throws LinuxSamplerException if optional timeout exceeded) |
168 |
vector<string> LSCPTest::receiveMultiLineAnswerFromLSCPServer(uint timeout_seconds) throw (LinuxSamplerException) { |
169 |
string msg = receiveAnswerFromLSCPServer("\n.\r\n", timeout_seconds); |
170 |
return __ConvertMultiLineMessage(msg); |
171 |
} |
172 |
|
173 |
/// wait until LSCP server answers with the given \a delimiter token at the end (throws LinuxSamplerException if optional timeout exceeded or socket error occured) |
174 |
string LSCPTest::receiveAnswerFromLSCPServer(string delimiter, uint timeout_seconds) throw (LinuxSamplerException) { |
175 |
if (!hServerIn) { |
176 |
cout << "receiveAnswerFromLSCPServer() error: client socket not ready\n" << flush; |
177 |
return ""; |
178 |
} |
179 |
string message; |
180 |
char c; |
181 |
fd_set sockSet; |
182 |
timeval timeout; |
183 |
|
184 |
while (true) { |
185 |
if (timeout_seconds) { |
186 |
FD_ZERO(&sockSet); |
187 |
FD_SET(hSocket, &sockSet); |
188 |
timeout.tv_sec = timeout_seconds; |
189 |
timeout.tv_usec = 0; |
190 |
int res = select(hSocket + 1, &sockSet, NULL, NULL, &timeout); |
191 |
if (!res) throw LinuxSamplerException("LSCPTest::receiveAnswerFromLSCPServer(): timeout (" + ToString(timeout_seconds) + "s) exceeded waiting for expected answer (end)"); |
192 |
else if (res < 0) throw LinuxSamplerException("LSCPTest::receiveAnswerFromLSCPServer(): select error"); |
193 |
} |
194 |
|
195 |
// there's something to read, so read one character |
196 |
c = fgetc(hServerIn); |
197 |
if (c == EOF) { |
198 |
cout << "receiveAnswerFromLSCPServer() error: EOF reached\n" << flush; |
199 |
return ""; |
200 |
} |
201 |
message += c; |
202 |
string::size_type pos = message.rfind(delimiter); // ouch, but this is only a test case, right? ;) |
203 |
if (pos != string::npos) return message; |
204 |
} |
205 |
} |
206 |
|
207 |
|
208 |
|
209 |
void LSCPTest::printTestSuiteName() { |
210 |
cout << "\b \nRunning LSCP Tests: " << flush; |
211 |
} |
212 |
|
213 |
void LSCPTest::setUp() { |
214 |
} |
215 |
|
216 |
void LSCPTest::tearDown() { |
217 |
} |
218 |
|
219 |
|
220 |
|
221 |
// Check if we can launch the LSCP Server (implies that there's no other instance running at the moment). |
222 |
void LSCPTest::testLaunchLSCPServer() { |
223 |
//cout << "testLaunchLSCPServer()\n" << flush; |
224 |
CPPUNIT_ASSERT(launchLSCPServer()); |
225 |
} |
226 |
|
227 |
// Check if we can connect a client connection to the LSCP server and close that connection without problems. |
228 |
void LSCPTest::testConnectToLSCPServer() { |
229 |
//cout << "testConnectToLSCPServer()\n" << flush; |
230 |
sleep(1); // wait 1s |
231 |
CPPUNIT_ASSERT(connectToLSCPServer()); |
232 |
sleep(2); // wait 2s |
233 |
CPPUNIT_ASSERT(closeConnectionToLSCPServer()); |
234 |
} |
235 |
|
236 |
// Check "ADD CHANNEL" LSCP command. |
237 |
void LSCPTest::test_ADD_CHANNEL() { |
238 |
sleep(1); // wait 1s |
239 |
CPPUNIT_ASSERT(connectToLSCPServer()); |
240 |
|
241 |
sendCommandToLSCPServer("ADD CHANNEL"); |
242 |
CPPUNIT_ASSERT(receiveSingleLineAnswerFromLSCPServer() == "OK[0]"); |
243 |
|
244 |
sendCommandToLSCPServer("ADD CHANNEL"); |
245 |
CPPUNIT_ASSERT(receiveSingleLineAnswerFromLSCPServer() == "OK[1]"); |
246 |
|
247 |
sendCommandToLSCPServer("ADD CHANNEL"); |
248 |
CPPUNIT_ASSERT(receiveSingleLineAnswerFromLSCPServer() == "OK[2]"); |
249 |
} |
250 |
|
251 |
// Check "GET CHANNELS" LSCP command. |
252 |
void LSCPTest::test_GET_CHANNELS() { |
253 |
sendCommandToLSCPServer("GET CHANNELS"); |
254 |
string answer = receiveSingleLineAnswerFromLSCPServer(); |
255 |
int initial_channels = atoi(answer.c_str()); |
256 |
|
257 |
// add sampler channels and check if the count increases |
258 |
for (uint trial = 1; trial <= 3; trial++) { |
259 |
sendCommandToLSCPServer("ADD CHANNEL"); |
260 |
answer = receiveSingleLineAnswerFromLSCPServer(); |
261 |
sendCommandToLSCPServer("GET CHANNELS"); |
262 |
answer = receiveSingleLineAnswerFromLSCPServer(); |
263 |
int channels = atoi(answer.c_str()); |
264 |
CPPUNIT_ASSERT(channels == initial_channels + trial); |
265 |
} |
266 |
} |
267 |
|
268 |
// Check "REMOVE CHANNEL" LSCP command. |
269 |
void LSCPTest::test_REMOVE_CHANNEL() { |
270 |
// how many channels do we have at the moment? |
271 |
sendCommandToLSCPServer("GET CHANNELS"); |
272 |
string answer = receiveSingleLineAnswerFromLSCPServer(); |
273 |
int initial_channels = atoi(answer.c_str()); |
274 |
|
275 |
// if there are no sampler channels yet, create some |
276 |
if (!initial_channels) { |
277 |
const uint create_channels = 4; |
278 |
for (uint i = 0; i < create_channels; i++) { |
279 |
sendCommandToLSCPServer("ADD CHANNEL"); |
280 |
answer = receiveSingleLineAnswerFromLSCPServer(); |
281 |
} |
282 |
initial_channels = create_channels; |
283 |
} |
284 |
|
285 |
// now remove the channels until there is no one left and check if we really need 'initial_channels' times to achieve that |
286 |
for (uint channels = initial_channels; channels; channels--) { |
287 |
sendCommandToLSCPServer("LIST CHANNELS"); |
288 |
answer = receiveSingleLineAnswerFromLSCPServer(); |
289 |
if (answer == "") CPPUNIT_ASSERT(false); // no sampler channel left already? -> failure |
290 |
|
291 |
// take the last channel number in the list which we will take to remove that sampler channel |
292 |
string::size_type pos = answer.rfind(","); |
293 |
string channel_to_remove = (pos != string::npos) ? answer.substr(pos + 1, answer.length() - (pos + 1)) /* "m,n,...,t */ |
294 |
: answer; /* "k" */ |
295 |
|
296 |
//cout << " channel_to_remove: \"" << channel_to_remove << "\"\n" << flush; |
297 |
|
298 |
// remove that channel |
299 |
sendCommandToLSCPServer("REMOVE CHANNEL " + channel_to_remove); |
300 |
answer = receiveSingleLineAnswerFromLSCPServer(); |
301 |
CPPUNIT_ASSERT(answer == "OK"); |
302 |
} |
303 |
CPPUNIT_ASSERT(true); // success |
304 |
} |
305 |
|
306 |
// Check "GET AUDIO_OUTPUT_CHANNEL_PARAMETER INFO" LSCP command. |
307 |
void LSCPTest::test_GET_AUDIO_OUTPUT_CHANNEL_PARAMETER_INFO() { |
308 |
// first check if there's already an audio output device created |
309 |
sendCommandToLSCPServer("GET AUDIO_OUTPUT_DEVICES"); |
310 |
string answer = receiveSingleLineAnswerFromLSCPServer(); |
311 |
int devices = atoi(answer.c_str()); |
312 |
CPPUNIT_ASSERT(devices >= 0); |
313 |
if (!devices) { // if there's no audio output device yet, try to create one |
314 |
sendCommandToLSCPServer("GET AVAILABLE_AUDIO_OUTPUT_DRIVERS"); |
315 |
string drivers = receiveSingleLineAnswerFromLSCPServer(); |
316 |
CPPUNIT_ASSERT(drivers.size()); |
317 |
|
318 |
// iterate through all available drivers until device creation was successful |
319 |
do { |
320 |
optional<string> driver = __ExtractFirstToken(&drivers, ","); |
321 |
CPPUNIT_ASSERT(driver); |
322 |
|
323 |
sendCommandToLSCPServer("CREATE AUDIO_OUTPUT_DEVICE " + *driver); |
324 |
answer = receiveSingleLineAnswerFromLSCPServer(); |
325 |
} while (answer != "OK[0]"); |
326 |
} |
327 |
|
328 |
// now we can check the "GET AUDIO_OUTPUT_CHANNEL_PARAMETER INFO" command |
329 |
const uint timeout_seconds = 2; |
330 |
sendCommandToLSCPServer("GET AUDIO_OUTPUT_CHANNEL_PARAMETER INFO 0 0 NAME"); |
331 |
vector<string> vAnswer = receiveMultiLineAnswerFromLSCPServer(timeout_seconds); |
332 |
CPPUNIT_ASSERT(vAnswer.size() >= 4); // should at least contain tags TYPE, DESCRIPTION, FIX and MULTIPLICITY |
333 |
|
334 |
sendCommandToLSCPServer("GET AUDIO_OUTPUT_CHANNEL_PARAMETER INFO 0 0 IS_MIX_CHANNEL"); |
335 |
vAnswer = receiveMultiLineAnswerFromLSCPServer(timeout_seconds); |
336 |
CPPUNIT_ASSERT(vAnswer.size() >= 4); // should at least contain tags TYPE, DESCRIPTION, FIX and MULTIPLICITY |
337 |
} |
338 |
|
339 |
// Check if we can shutdown the LSCP Server without problems. |
340 |
void LSCPTest::testShutdownLSCPServer() { |
341 |
//cout << "testShutdownLSCPServer()\n" << flush; |
342 |
sleep(2); // wait 2s |
343 |
CPPUNIT_ASSERT(closeConnectionToLSCPServer()); |
344 |
sleep(3); // wait 3s |
345 |
CPPUNIT_ASSERT(shutdownLSCPServer()); |
346 |
} |