1 |
schoenebeck |
211 |
#include "LSCPTest.h" |
2 |
|
|
|
3 |
schoenebeck |
217 |
#include "../common/global.h" |
4 |
|
|
#include "../common/optional.h" |
5 |
|
|
|
6 |
schoenebeck |
211 |
#include <iostream> |
7 |
|
|
#include <stdio.h> |
8 |
|
|
#include <stdlib.h> |
9 |
schoenebeck |
217 |
#include <unistd.h> |
10 |
schoenebeck |
211 |
|
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 |
schoenebeck |
217 |
/// 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 |
schoenebeck |
211 |
// 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 |
schoenebeck |
217 |
/// 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 |
schoenebeck |
211 |
return __ConvertMultiLineMessage(msg); |
171 |
|
|
} |
172 |
|
|
|
173 |
schoenebeck |
217 |
/// 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 |
schoenebeck |
211 |
if (!hServerIn) { |
176 |
|
|
cout << "receiveAnswerFromLSCPServer() error: client socket not ready\n" << flush; |
177 |
|
|
return ""; |
178 |
|
|
} |
179 |
|
|
string message; |
180 |
|
|
char c; |
181 |
schoenebeck |
217 |
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 |
schoenebeck |
211 |
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 |
schoenebeck |
217 |
// 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 |
schoenebeck |
211 |
// 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 |
|
|
} |