]> kaliko git repositories - python-musicpd.git/blob - test.py
Update tests, improve test on iterating
[python-musicpd.git] / test.py
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3 """
4 Test suite highly borrowed^Wsteal from python-mpd2 [0] project.
5
6 [0] https://github.com/Mic92/python-mpd2
7 """
8
9
10 import itertools
11 import sys
12 import types
13 import unittest
14 import warnings
15
16 import musicpd
17
18 try:
19     import unittest.mock as mock
20 except ImportError:
21     try:
22         import mock
23     except ImportError:
24         print("Please install mock from PyPI to run tests!")
25         sys.exit(1)
26
27 # show deprecation warnings
28 warnings.simplefilter('default')
29
30
31 TEST_MPD_HOST, TEST_MPD_PORT = ('example.com', 10000)
32
33
34 class TestMPDClient(unittest.TestCase):
35
36     longMessage = True
37     # last sync: musicpd 0.4.2 unreleased / Mon Nov 17 21:45:22 CET 2014
38     commands = {
39             # Status Commands
40             'clearerror':         'nothing',
41             'currentsong':        'object',
42             'idle':               'list',
43             'noidle':             None,
44             'status':             'object',
45             'stats':              'object',
46             # Playback Option Commands
47             'consume':            'nothing',
48             'crossfade':          'nothing',
49             'mixrampdb':          'nothing',
50             'mixrampdelay':       'nothing',
51             'random':             'nothing',
52             'repeat':             'nothing',
53             'setvol':             'nothing',
54             'single':             'nothing',
55             'replay_gain_mode':   'nothing',
56             'replay_gain_status': 'item',
57             'volume':             'nothing',
58             # Playback Control Commands
59             'next':               'nothing',
60             'pause':              'nothing',
61             'play':               'nothing',
62             'playid':             'nothing',
63             'previous':           'nothing',
64             'seek':               'nothing',
65             'seekid':             'nothing',
66             'seekcur':            'nothing',
67             'stop':               'nothing',
68             # Playlist Commands
69             'add':                'nothing',
70             'addid':              'item',
71             'clear':              'nothing',
72             'delete':             'nothing',
73             'deleteid':           'nothing',
74             'move':               'nothing',
75             'moveid':             'nothing',
76             'playlist':           'playlist',
77             'playlistfind':       'songs',
78             'playlistid':         'songs',
79             'playlistinfo':       'songs',
80             'playlistsearch':     'songs',
81             'plchanges':          'songs',
82             'plchangesposid':     'changes',
83             'prio':               'nothing',
84             'prioid':             'nothing',
85             'rangeid':            'nothing',
86             'shuffle':            'nothing',
87             'swap':               'nothing',
88             'swapid':             'nothing',
89             'addtagid':           'nothing',
90             'cleartagid':         'nothing',
91             # Stored Playlist Commands
92             'listplaylist':       'list',
93             'listplaylistinfo':   'songs',
94             'listplaylists':      'playlists',
95             'load':               'nothing',
96             'playlistadd':        'nothing',
97             'playlistclear':      'nothing',
98             'playlistdelete':     'nothing',
99             'playlistmove':       'nothing',
100             'rename':             'nothing',
101             'rm':                 'nothing',
102             'save':               'nothing',
103             # Database Commands
104             'count':              'object',
105             'find':               'songs',
106             'findadd':            'nothing',
107             'list':               'list',
108             'listall':            'database',
109             'listallinfo':        'database',
110             'lsinfo':             'database',
111             'search':             'songs',
112             'searchadd':          'nothing',
113             'searchaddpl':        'nothing',
114             'update':             'item',
115             'rescan':             'item',
116             'readcomments':       'object',
117             # Mounts and neighbors
118             'mount':              'nothing',
119             'unmount':            'nothing',
120             'listmounts':         'mounts',
121             'listneighbors':      'neighbors',
122             # Sticker Commands
123             'sticker get':        'item',
124             'sticker set':        'nothing',
125             'sticker delete':     'nothing',
126             'sticker list':       'list',
127             'sticker find':       'songs',
128             # Connection Commands
129             'close':              None,
130             'kill':               None,
131             'password':           'nothing',
132             'ping':               'nothing',
133             # Audio Output Commands
134             'disableoutput':      'nothing',
135             'enableoutput':       'nothing',
136             'toggleoutput':       'nothing',
137             'outputs':            'outputs',
138             # Reflection Commands
139             'config':             'object',
140             'commands':           'list',
141             'notcommands':        'list',
142             'tagtypes':           'list',
143             'urlhandlers':        'list',
144             'decoders':           'plugins',
145             # Client to Client
146             'subscribe':          'nothing',
147             'unsubscribe':        'nothing',
148             'channels':           'list',
149             'readmessages':       'messages',
150             'sendmessage':        'nothing',
151         }
152
153     def setUp(self):
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)]
157
158         self.socket_mock.socket.side_effect = (
159             lambda *a, **kw:
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'))
166
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')
171
172     def tearDown(self):
173         self.socket_patch.stop()
174
175     def MPDWillReturn(self, *lines):
176         # Return what the caller wants first, then do as if the socket was
177         # disconnected.
178         self.client._rfile.readline.side_effect = itertools.chain(
179             lines, itertools.repeat(''))
180
181     def assertMPDReceived(self, *lines):
182         self.client._wfile.write.assert_called_with(*lines)
183
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))
189             if ' ' in cmd:
190                 self.assertTrue(hasattr(self.client, cmd.replace(' ', '_')))
191
192     def test_fetch_nothing(self):
193         self.MPDWillReturn('OK\n')
194         self.assertIsNone(self.client.ping())
195         self.assertMPDReceived('ping\n')
196
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')
201
202     def test_fetch_item(self):
203         self.MPDWillReturn('updating_db: 42\n', 'OK\n')
204         self.assertIsNotNone(self.client.update())
205
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)
212
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)
218
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()
222
223         self.assertMPDReceived('playlistinfo\n')
224         self.assertIsInstance(playlist, list)
225         self.assertEqual(1, len(playlist))
226         e = playlist[0]
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'])
231
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')
237
238         status = self.client.fetch_status()
239         self.assertEqual(1, self.client._wfile.write.call_count)
240         self.assertEqual({'volume': '50'}, status)
241
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)
257
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')
265         self.client.status()
266         self.assertMPDReceived('status\n')
267
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)
277
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)
282
283     def test_client_to_client(self):
284         # client to client is at this time in beta!
285
286         self.MPDWillReturn('OK\n')
287         self.assertIsNone(self.client.subscribe("monty"))
288         self.assertMPDReceived('subscribe "monty"\n')
289
290         self.MPDWillReturn('channel: monty\n', 'OK\n')
291         channels = self.client.channels()
292         self.assertMPDReceived('channels\n')
293         self.assertEqual(['monty'], channels)
294
295         self.MPDWillReturn('OK\n')
296         self.assertIsNone(self.client.sendmessage('monty', 'SPAM'))
297         self.assertMPDReceived('sendmessage "monty" "SPAM"\n')
298
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'}])
303
304         self.MPDWillReturn('OK\n')
305         self.assertIsNone(self.client.unsubscribe('monty'))
306         self.assertMPDReceived('unsubscribe "monty"\n')
307
308         self.MPDWillReturn('OK\n')
309         channels = self.client.channels()
310         self.assertMPDReceived('channels\n')
311         self.assertEqual([], channels)
312
313     def test_ranges_in_command_args(self):
314         self.MPDWillReturn('OK\n')
315         self.client.playlistinfo((10,))
316         self.assertMPDReceived('playlistinfo 10:\n')
317
318         self.MPDWillReturn('OK\n')
319         self.client.playlistinfo(('10',))
320         self.assertMPDReceived('playlistinfo 10:\n')
321
322         self.MPDWillReturn('OK\n')
323         self.client.playlistinfo((10, 12))
324         self.assertMPDReceived('playlistinfo 10:12\n')
325
326         self.MPDWillReturn('OK\n')
327         self.client.rangeid(())
328         self.assertMPDReceived('rangeid :\n')
329
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)
334
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')
339
340     def test_commands_without_callbacks(self):
341         self.MPDWillReturn('\n')
342         self.client.close()
343         self.assertMPDReceived('close\n')
344
345         # XXX: what are we testing here?
346         self.client._reset()
347         self.client.connect(TEST_MPD_HOST, TEST_MPD_PORT)
348
349     def test_connection_lost(self):
350         # Simulate a connection lost: the socket returns empty strings
351         self.MPDWillReturn('')
352
353         with self.assertRaises(musicpd.ConnectionError):
354             self.client.status()
355
356         # consistent behaviour
357         # solves <https://github.com/Mic92/python-mpd2/issues/11> (also present
358         # in python-mpd)
359         with self.assertRaises(musicpd.ConnectionError):
360             self.client.status()
361
362         self.assertIs(self.client._sock, None)
363
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')
368
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)
373
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)
378
379
380 if __name__ == '__main__':
381     unittest.main()