2 # -*- coding: utf-8 -*-
4 Test suite highly borrowed^Wsteal from python-mpd2 [0] project.
6 [0] https://github.com/Mic92/python-mpd2
19 import unittest.mock as mock
24 print("Please install mock from PyPI to run tests!")
27 # show deprecation warnings
28 warnings.simplefilter('default')
31 TEST_MPD_HOST, TEST_MPD_PORT = ('example.com', 10000)
34 class TestMPDClient(unittest.TestCase):
37 # last sync: musicpd 0.4.2 unreleased / Mon Nov 17 21:45:22 CET 2014
40 'clearerror': 'nothing',
41 'currentsong': 'object',
46 # Playback Option Commands
48 'crossfade': 'nothing',
49 'mixrampdb': 'nothing',
50 'mixrampdelay': 'nothing',
55 'replay_gain_mode': 'nothing',
56 'replay_gain_status': 'item',
58 # Playback Control Commands
63 'previous': 'nothing',
73 'deleteid': 'nothing',
76 'playlist': 'playlist',
77 'playlistfind': 'songs',
78 'playlistid': 'songs',
79 'playlistinfo': 'songs',
80 'playlistsearch': 'songs',
82 'plchangesposid': 'changes',
89 'addtagid': 'nothing',
90 'cleartagid': 'nothing',
91 # Stored Playlist Commands
92 'listplaylist': 'list',
93 'listplaylistinfo': 'songs',
94 'listplaylists': 'playlists',
96 'playlistadd': 'nothing',
97 'playlistclear': 'nothing',
98 'playlistdelete': 'nothing',
99 'playlistmove': 'nothing',
106 'findadd': 'nothing',
108 'listall': 'database',
109 'listallinfo': 'database',
110 'lsinfo': 'database',
112 'searchadd': 'nothing',
113 'searchaddpl': 'nothing',
116 'readcomments': 'object',
117 # Mounts and neighbors
119 'unmount': 'nothing',
120 'listmounts': 'mounts',
121 'listneighbors': 'neighbors',
123 'sticker get': 'item',
124 'sticker set': 'nothing',
125 'sticker delete': 'nothing',
126 'sticker list': 'list',
127 'sticker find': 'songs',
128 # Connection Commands
131 'password': 'nothing',
133 # Audio Output Commands
134 'disableoutput': 'nothing',
135 'enableoutput': 'nothing',
136 'toggleoutput': 'nothing',
137 'outputs': 'outputs',
138 # Reflection Commands
141 'notcommands': 'list',
143 'urlhandlers': 'list',
144 'decoders': 'plugins',
146 'subscribe': 'nothing',
147 'unsubscribe': 'nothing',
149 'readmessages': 'messages',
150 'sendmessage': 'nothing',
154 self.socket_patch = mock.patch('musicpd.socket')
155 self.socket_mock = self.socket_patch.start()
156 self.socket_mock.getaddrinfo.return_value = [range(5)]
158 self.socket_mock.socket.side_effect = (
160 # Create a new socket.socket() mock with default attributes,
161 # each time we are calling it back (otherwise, it keeps set
162 # attributes across calls).
163 # That's probably what we want, since reconnecting is like
164 # reinitializing the entire connection, and so, the mock.
165 mock.MagicMock(name='socket.socket'))
167 self.client = musicpd.MPDClient()
168 self.client.connect(TEST_MPD_HOST, TEST_MPD_PORT)
169 self.client._sock.reset_mock()
170 self.MPDWillReturn('ACK don\'t forget to setup your mock\n')
173 self.socket_patch.stop()
175 def MPDWillReturn(self, *lines):
176 # Return what the caller wants first, then do as if the socket was
178 self.client._rfile.readline.side_effect = itertools.chain(
179 lines, itertools.repeat(''))
181 def assertMPDReceived(self, *lines):
182 self.client._wfile.write.assert_called_with(*lines)
184 def test_metaclass_commands(self):
185 """Controls client has at least commands as last synchronized in
186 TestMPDClient.commands"""
187 for cmd, ret in TestMPDClient.commands.items():
188 self.assertTrue(hasattr(self.client, cmd), msg='cmd "{}" not available!'.format(cmd))
190 self.assertTrue(hasattr(self.client, cmd.replace(' ', '_')))
192 def test_fetch_nothing(self):
193 self.MPDWillReturn('OK\n')
194 self.assertIsNone(self.client.ping())
195 self.assertMPDReceived('ping\n')
197 def test_fetch_list(self):
198 self.MPDWillReturn('OK\n')
199 self.assertIsInstance(self.client.list('album'), list)
200 self.assertMPDReceived('list "album"\n')
202 def test_fetch_item(self):
203 self.MPDWillReturn('updating_db: 42\n', 'OK\n')
204 self.assertIsNotNone(self.client.update())
206 def test_fetch_object(self):
207 # XXX: _read_objects() doesn't wait for the final OK
208 self.MPDWillReturn('volume: 63\n', 'OK\n')
209 status = self.client.status()
210 self.assertMPDReceived('status\n')
211 self.assertIsInstance(status, dict)
213 # XXX: _read_objects() doesn't wait for the final OK
214 self.MPDWillReturn('OK\n')
215 stats = self.client.stats()
216 self.assertMPDReceived('stats\n')
217 self.assertIsInstance(stats, dict)
219 def test_fetch_songs(self):
220 self.MPDWillReturn('file: my-song.ogg\n', 'Pos: 0\n', 'Id: 66\n', 'OK\n')
221 playlist = self.client.playlistinfo()
223 self.assertMPDReceived('playlistinfo\n')
224 self.assertIsInstance(playlist, list)
225 self.assertEqual(1, len(playlist))
227 self.assertIsInstance(e, dict)
228 self.assertEqual('my-song.ogg', e['file'])
229 self.assertEqual('0', e['pos'])
230 self.assertEqual('66', e['id'])
232 def test_send_and_fetch(self):
233 self.MPDWillReturn('volume: 50\n', 'OK\n')
234 result = self.client.send_status()
235 self.assertEqual(None, result)
236 self.assertMPDReceived('status\n')
238 status = self.client.fetch_status()
239 self.assertEqual(1, self.client._wfile.write.call_count)
240 self.assertEqual({'volume': '50'}, status)
242 def test_iterating(self):
243 self.MPDWillReturn('file: my-song.ogg\n', 'Pos: 0\n', 'Id: 66\n',
244 'file: my-song.ogg\n', 'Pos: 0\n', 'Id: 66\n', 'OK\n')
245 self.client.iterate = True
246 playlist = self.client.playlistinfo()
247 self.assertMPDReceived('playlistinfo\n')
248 self.assertIsInstance(playlist, types.GeneratorType)
249 self.assertTrue(self.client._iterating)
250 for song in playlist:
251 self.assertRaises(musicpd.IteratingError, self.client.status)
252 self.assertIsInstance(song, dict)
253 self.assertEqual('my-song.ogg', song['file'])
254 self.assertEqual('0', song['pos'])
255 self.assertEqual('66', song['id'])
256 self.assertFalse(self.client._iterating)
258 def test_noidle(self):
259 self.MPDWillReturn('OK\n') # nothing changed after idle-ing
260 self.client.send_idle()
261 self.MPDWillReturn('OK\n') # nothing changed after noidle
262 self.assertEqual(self.client.noidle(), [])
263 self.assertMPDReceived('noidle\n')
264 self.MPDWillReturn('volume: 50\n', 'OK\n')
266 self.assertMPDReceived('status\n')
268 def test_noidle_while_idle_started_sending(self):
269 self.MPDWillReturn('OK\n') # nothing changed after idle
270 self.client.send_idle()
271 self.MPDWillReturn('CHANGED: player\n', 'OK\n') # noidle response
272 self.assertEqual(self.client.noidle(), ['player'])
273 self.MPDWillReturn('volume: 50\n', 'OK\n')
274 status = self.client.status()
275 self.assertMPDReceived('status\n')
276 self.assertEqual({'volume': '50'}, status)
278 def test_throw_when_calling_noidle_withoutidling(self):
279 self.assertRaises(musicpd.CommandError, self.client.noidle)
280 self.client.send_status()
281 self.assertRaises(musicpd.CommandError, self.client.noidle)
283 def test_client_to_client(self):
284 # client to client is at this time in beta!
286 self.MPDWillReturn('OK\n')
287 self.assertIsNone(self.client.subscribe("monty"))
288 self.assertMPDReceived('subscribe "monty"\n')
290 self.MPDWillReturn('channel: monty\n', 'OK\n')
291 channels = self.client.channels()
292 self.assertMPDReceived('channels\n')
293 self.assertEqual(['monty'], channels)
295 self.MPDWillReturn('OK\n')
296 self.assertIsNone(self.client.sendmessage('monty', 'SPAM'))
297 self.assertMPDReceived('sendmessage "monty" "SPAM"\n')
299 self.MPDWillReturn('channel: monty\n', 'message: SPAM\n', 'OK\n')
300 msg = self.client.readmessages()
301 self.assertMPDReceived('readmessages\n')
302 self.assertEqual(msg, [{'channel': 'monty', 'message': 'SPAM'}])
304 self.MPDWillReturn('OK\n')
305 self.assertIsNone(self.client.unsubscribe('monty'))
306 self.assertMPDReceived('unsubscribe "monty"\n')
308 self.MPDWillReturn('OK\n')
309 channels = self.client.channels()
310 self.assertMPDReceived('channels\n')
311 self.assertEqual([], channels)
313 def test_ranges_in_command_args(self):
314 self.MPDWillReturn('OK\n')
315 self.client.playlistinfo((10,))
316 self.assertMPDReceived('playlistinfo 10:\n')
318 self.MPDWillReturn('OK\n')
319 self.client.playlistinfo(('10',))
320 self.assertMPDReceived('playlistinfo 10:\n')
322 self.MPDWillReturn('OK\n')
323 self.client.playlistinfo((10, 12))
324 self.assertMPDReceived('playlistinfo 10:12\n')
326 self.MPDWillReturn('OK\n')
327 self.client.rangeid(())
328 self.assertMPDReceived('rangeid :\n')
330 for arg in [(10, 't'), (10, 1, 1), (None,1)]:
331 self.MPDWillReturn('OK\n')
332 with self.assertRaises(musicpd.CommandError):
333 self.client.playlistinfo(arg)
335 def test_numbers_as_command_args(self):
336 self.MPDWillReturn('OK\n')
337 self.client.find('file', 1)
338 self.assertMPDReceived('find "file" "1"\n')
340 def test_commands_without_callbacks(self):
341 self.MPDWillReturn('\n')
343 self.assertMPDReceived('close\n')
345 # XXX: what are we testing here?
347 self.client.connect(TEST_MPD_HOST, TEST_MPD_PORT)
349 def test_connection_lost(self):
350 # Simulate a connection lost: the socket returns empty strings
351 self.MPDWillReturn('')
353 with self.assertRaises(musicpd.ConnectionError):
356 # consistent behaviour
357 # solves <https://github.com/Mic92/python-mpd2/issues/11> (also present
359 with self.assertRaises(musicpd.ConnectionError):
362 self.assertIs(self.client._sock, None)
364 def test_parse_sticker_get_no_sticker(self):
365 self.MPDWillReturn('ACK [50@0] {sticker} no such sticker\n')
366 self.assertRaises(musicpd.CommandError,
367 self.client.sticker_get, 'song', 'baz', 'foo')
369 def test_parse_sticker_list(self):
370 self.MPDWillReturn('sticker: foo=bar\n', 'sticker: lom=bok\n', 'OK\n')
371 res = self.client.sticker_list('song', 'baz')
372 self.assertEqual(['foo=bar', 'lom=bok'], res)
374 # Even with only one sticker, we get a dict
375 self.MPDWillReturn('sticker: foo=bar\n', 'OK\n')
376 res = self.client.sticker_list('song', 'baz')
377 self.assertEqual(['foo=bar'], res)
380 if __name__ == '__main__':