]> kaliko git repositories - python-musicpd.git/blob - test.py
Add rangeid command
[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.0
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             "shuffle":            "nothing",
84             "swap":               "nothing",
85             "swapid":             "nothing",
86             # Stored Playlist Commands
87             "listplaylist":       "list",
88             "listplaylistinfo":   "songs",
89             "listplaylists":      "playlists",
90             "load":               "nothing",
91             "playlistadd":        "nothing",
92             "playlistclear":      "nothing",
93             "playlistdelete":     "nothing",
94             "playlistmove":       "nothing",
95             "rename":             "nothing",
96             "rm":                 "nothing",
97             "save":               "nothing",
98             # Database Commands
99             "count":              "object",
100             "find":               "songs",
101             "findadd":            "nothing",
102             "list":               "list",
103             "listall":            "database",
104             "listallinfo":        "database",
105             "lsinfo":             "database",
106             "search":             "songs",
107             "searchadd":          "nothing",
108             "searchaddpl":        "nothing",
109             "update":             "item",
110             "rescan":             "item",
111             "readcomments":       "object",
112             # Sticker Commands
113             "sticker get":        "item",
114             "sticker set":        "nothing",
115             "sticker delete":     "nothing",
116             "sticker list":       "list",
117             "sticker find":       "songs",
118             # Connection Commands
119             "close":              None,
120             "kill":               None,
121             "password":           "nothing",
122             "ping":               "nothing",
123             # Audio Output Commands
124             "disableoutput":      "nothing",
125             "enableoutput":       "nothing",
126             "toggleoutput":       "nothing",
127             "outputs":            "outputs",
128             # Reflection Commands
129             "commands":           "list",
130             "notcommands":        "list",
131             "tagtypes":           "list",
132             "urlhandlers":        "list",
133             "decoders":           "plugins",
134             # Client to Client
135             "subscribe":          "nothing",
136             "unsubscribe":        "nothing",
137             "channels":           "list",
138             "readmessages":       "messages",
139             "sendmessage":        "nothing",
140         }
141
142     def setUp(self):
143         self.socket_patch = mock.patch("musicpd.socket")
144         self.socket_mock = self.socket_patch.start()
145         self.socket_mock.getaddrinfo.return_value = [range(5)]
146
147         self.socket_mock.socket.side_effect = (
148             lambda *a, **kw:
149             # Create a new socket.socket() mock with default attributes,
150             # each time we are calling it back (otherwise, it keeps set
151             # attributes across calls).
152             # That's probably what we want, since reconnecting is like
153             # reinitializing the entire connection, and so, the mock.
154             mock.MagicMock(name="socket.socket"))
155
156         self.client = musicpd.MPDClient()
157         self.client.connect(TEST_MPD_HOST, TEST_MPD_PORT)
158         self.client._sock.reset_mock()
159         self.MPDWillReturn("ACK don't forget to setup your mock\n")
160
161     def tearDown(self):
162         self.socket_patch.stop()
163
164     def MPDWillReturn(self, *lines):
165         # Return what the caller wants first, then do as if the socket was
166         # disconnected.
167         self.client._rfile.readline.side_effect = itertools.chain(
168             lines, itertools.repeat(''))
169
170     def assertMPDReceived(self, *lines):
171         self.client._wfile.write.assert_called_with(*lines)
172
173     def test_metaclass_commands(self):
174         for cmd, ret in TestMPDClient.commands.items():
175             self.assertTrue(hasattr(self.client, cmd), msg='fails for{}'.format(cmd))
176             if ' ' in cmd:
177                 self.assertTrue(hasattr(self.client, cmd.replace(' ', '_')))
178
179     def test_fetch_nothing(self):
180         self.MPDWillReturn('OK\n')
181         self.assertIsNone(self.client.ping())
182         self.assertMPDReceived('ping\n')
183
184     def test_fetch_list(self):
185         self.MPDWillReturn('OK\n')
186         self.assertIsInstance(self.client.list('album'), list)
187         self.assertMPDReceived('list "album"\n')
188
189     def test_fetch_item(self):
190         self.MPDWillReturn('updating_db: 42\n', 'OK\n')
191         self.assertIsNotNone(self.client.update())
192
193     def test_fetch_object(self):
194         # XXX: _read_objects() doesn't wait for the final OK
195         self.MPDWillReturn('volume: 63\n', 'OK\n')
196         status = self.client.status()
197         self.assertMPDReceived('status\n')
198         self.assertIsInstance(status, dict)
199
200         # XXX: _read_objects() doesn't wait for the final OK
201         self.MPDWillReturn('OK\n')
202         stats = self.client.stats()
203         self.assertMPDReceived('stats\n')
204         self.assertIsInstance(stats, dict)
205
206     def test_fetch_songs(self):
207         self.MPDWillReturn("file: my-song.ogg\n", "Pos: 0\n", "Id: 66\n", "OK\n")
208         playlist = self.client.playlistinfo()
209
210         self.assertMPDReceived('playlistinfo\n')
211         self.assertIsInstance(playlist, list)
212         self.assertEqual(1, len(playlist))
213         e = playlist[0]
214         self.assertIsInstance(e, dict)
215         self.assertEqual('my-song.ogg', e['file'])
216         self.assertEqual('0', e['pos'])
217         self.assertEqual('66', e['id'])
218
219     def test_send_and_fetch(self):
220         self.MPDWillReturn("volume: 50\n", "OK\n")
221         result = self.client.send_status()
222         self.assertEqual(None, result)
223         self.assertMPDReceived('status\n')
224
225         status = self.client.fetch_status()
226         self.assertEqual(1, self.client._wfile.write.call_count)
227         self.assertEqual({'volume': '50'}, status)
228
229     def test_iterating(self):
230         self.MPDWillReturn("file: my-song.ogg\n", "Pos: 0\n", "Id: 66\n", "OK\n")
231         self.client.iterate = True
232         playlist = self.client.playlistinfo()
233         self.assertMPDReceived('playlistinfo\n')
234         self.assertIsInstance(playlist, types.GeneratorType)
235         for song in playlist:
236             self.assertIsInstance(song, dict)
237             self.assertEqual('my-song.ogg', song['file'])
238             self.assertEqual('0', song['pos'])
239             self.assertEqual('66', song['id'])
240
241     def test_noidle(self):
242         self.MPDWillReturn('OK\n') # nothing changed after idle-ing
243         self.client.send_idle()
244         self.MPDWillReturn('OK\n') # nothing changed after noidle
245         self.assertEqual(self.client.noidle(), [])
246         self.assertMPDReceived('noidle\n')
247         self.MPDWillReturn("volume: 50\n", "OK\n")
248         self.client.status()
249         self.assertMPDReceived('status\n')
250
251     def test_noidle_while_idle_started_sending(self):
252         self.MPDWillReturn('OK\n') # nothing changed after idle
253         self.client.send_idle()
254         self.MPDWillReturn('CHANGED: player\n', 'OK\n')  # noidle response
255         self.assertEqual(self.client.noidle(), ['player'])
256         self.MPDWillReturn("volume: 50\n", "OK\n")
257         status = self.client.status()
258         self.assertMPDReceived('status\n')
259         self.assertEqual({'volume': '50'}, status)
260
261     def test_throw_when_calling_noidle_withoutidling(self):
262         self.assertRaises(musicpd.CommandError, self.client.noidle)
263         self.client.send_status()
264         self.assertRaises(musicpd.CommandError, self.client.noidle)
265
266     def test_client_to_client(self):
267         # client to client is at this time in beta!
268
269         self.MPDWillReturn('OK\n')
270         self.assertIsNone(self.client.subscribe("monty"))
271         self.assertMPDReceived('subscribe "monty"\n')
272
273         self.MPDWillReturn('channel: monty\n', 'OK\n')
274         channels = self.client.channels()
275         self.assertMPDReceived('channels\n')
276         self.assertEqual(["monty"], channels)
277
278         self.MPDWillReturn('OK\n')
279         self.assertIsNone(self.client.sendmessage("monty", "SPAM"))
280         self.assertMPDReceived('sendmessage "monty" "SPAM"\n')
281
282         self.MPDWillReturn('channel: monty\n', 'message: SPAM\n', 'OK\n')
283         msg = self.client.readmessages()
284         self.assertMPDReceived('readmessages\n')
285         self.assertEqual(msg, [{"channel":"monty", "message": "SPAM"}])
286
287         self.MPDWillReturn('OK\n')
288         self.assertIsNone(self.client.unsubscribe("monty"))
289         self.assertMPDReceived('unsubscribe "monty"\n')
290
291         self.MPDWillReturn('OK\n')
292         channels = self.client.channels()
293         self.assertMPDReceived('channels\n')
294         self.assertEqual([], channels)
295
296     def test_ranges_in_command_args(self):
297         self.MPDWillReturn("OK\n")
298         self.client.playlistinfo((10,))
299         self.assertMPDReceived('playlistinfo 10:\n')
300
301         self.MPDWillReturn("OK\n")
302         self.client.playlistinfo(("10",))
303         self.assertMPDReceived('playlistinfo 10:\n')
304
305         self.MPDWillReturn("OK\n")
306         self.client.playlistinfo((10, 12))
307         self.assertMPDReceived('playlistinfo 10:12\n')
308
309         self.MPDWillReturn("OK\n")
310         self.client.rangeid(())
311         self.assertMPDReceived('rangeid :\n')
312
313
314         for arg in [(10, "t"), (10, 1, 1), (None,1)]:
315             self.MPDWillReturn("OK\n")
316             with self.assertRaises(musicpd.CommandError):
317                 self.client.playlistinfo(arg)
318
319     def test_numbers_as_command_args(self):
320         self.MPDWillReturn("OK\n")
321         self.client.find("file", 1)
322         self.assertMPDReceived('find "file" "1"\n')
323
324     def test_commands_without_callbacks(self):
325         self.MPDWillReturn("\n")
326         self.client.close()
327         self.assertMPDReceived('close\n')
328
329         # XXX: what are we testing here?
330         self.client._reset()
331         self.client.connect(TEST_MPD_HOST, TEST_MPD_PORT)
332
333     def test_connection_lost(self):
334         # Simulate a connection lost: the socket returns empty strings
335         self.MPDWillReturn('')
336
337         with self.assertRaises(musicpd.ConnectionError):
338             self.client.status()
339
340         # consistent behaviour
341         # solves <https://github.com/Mic92/python-mpd2/issues/11> (also present
342         # in python-mpd)
343         with self.assertRaises(musicpd.ConnectionError):
344             self.client.status()
345
346         self.assertIs(self.client._sock, None)
347
348     def test_parse_sticker_get_no_sticker(self):
349         self.MPDWillReturn("ACK [50@0] {sticker} no such sticker\n")
350         self.assertRaises(musicpd.CommandError,
351                           self.client.sticker_get, 'song', 'baz', 'foo')
352
353     def test_parse_sticker_list(self):
354         self.MPDWillReturn("sticker: foo=bar\n", "sticker: lom=bok\n", "OK\n")
355         res = self.client.sticker_list('song', 'baz')
356         self.assertEqual(['foo=bar', 'lom=bok'], res)
357
358         # Even with only one sticker, we get a dict
359         self.MPDWillReturn("sticker: foo=bar\n", "OK\n")
360         res = self.client.sticker_list('song', 'baz')
361         self.assertEqual(['foo=bar'], res)
362
363
364 if __name__ == '__main__':
365     unittest.main()