]> kaliko git repositories - python-musicpd.git/blob - test.py
Improved socket timeout setter, add tests
[python-musicpd.git] / test.py
1 #!/usr/bin/env python3
2 # coding: utf-8
3 # SPDX-FileCopyrightText: 2012-2023  kaliko <kaliko@azylum.org>
4 # SPDX-License-Identifier: LGPL-3.0-or-later
5 # pylint: disable=missing-docstring
6 """
7 Test suite highly borrowed^Wsteal from python-mpd2 [0] project.
8
9 [0] https://github.com/Mic92/python-mpd2
10 """
11
12
13 import itertools
14 import os
15 import types
16 import unittest
17 import unittest.mock as mock
18 import warnings
19
20 import musicpd
21
22
23 # show deprecation warnings
24 warnings.simplefilter('default')
25
26
27 TEST_MPD_HOST, TEST_MPD_PORT = ('example.com', 10000)
28
29
30 class testEnvVar(unittest.TestCase):
31
32     def test_envvar(self):
33         # mock "os.path.exists" here to ensure there are no socket in
34         # XDG_RUNTIME_DIR/mpd or /run/mpd since with test defaults fallbacks
35         # when :
36         #   * neither MPD_HOST nor XDG_RUNTIME_DIR are not set
37         #   * /run/mpd does not expose a socket
38         with mock.patch('os.path.exists', return_value=False):
39             os.environ.pop('MPD_HOST', None)
40             os.environ.pop('MPD_PORT', None)
41             client = musicpd.MPDClient()
42             self.assertEqual(client.host, 'localhost')
43             self.assertEqual(client.port, '6600')
44
45             os.environ.pop('MPD_HOST', None)
46             os.environ['MPD_PORT'] = '6666'
47             client = musicpd.MPDClient()
48             self.assertEqual(client.pwd, None)
49             self.assertEqual(client.host, 'localhost')
50             self.assertEqual(client.port, '6666')
51
52         # Test password extraction
53         os.environ['MPD_HOST'] = 'pa55w04d@example.org'
54         client = musicpd.MPDClient()
55         self.assertEqual(client.pwd, 'pa55w04d')
56         self.assertEqual(client.host, 'example.org')
57
58         # Test host alone
59         os.environ['MPD_HOST'] = 'example.org'
60         client = musicpd.MPDClient()
61         self.assertFalse(client.pwd)
62         self.assertEqual(client.host, 'example.org')
63
64         # Test password extraction (no host)
65         os.environ['MPD_HOST'] = 'pa55w04d@'
66         with mock.patch('os.path.exists', return_value=False):
67             client = musicpd.MPDClient()
68         self.assertEqual(client.pwd, 'pa55w04d')
69         self.assertEqual(client.host, 'localhost')
70
71         # Test badly formatted MPD_HOST
72         os.environ['MPD_HOST'] = '@'
73         with mock.patch('os.path.exists', return_value=False):
74             client = musicpd.MPDClient()
75         self.assertEqual(client.pwd, None)
76         self.assertEqual(client.host, 'localhost')
77
78         # Test unix socket extraction
79         os.environ['MPD_HOST'] = 'pa55w04d@/unix/sock'
80         client = musicpd.MPDClient()
81         self.assertEqual(client.host, '/unix/sock')
82
83         # Test plain abstract socket extraction
84         os.environ['MPD_HOST'] = '@abstract'
85         client = musicpd.MPDClient()
86         self.assertEqual(client.host, '@abstract')
87
88         # Test password and abstract socket extraction
89         os.environ['MPD_HOST'] = 'pass@@abstract'
90         client = musicpd.MPDClient()
91         self.assertEqual(client.pwd, 'pass')
92         self.assertEqual(client.host, '@abstract')
93
94         # Test unix socket fallback
95         os.environ.pop('MPD_HOST', None)
96         os.environ.pop('MPD_PORT', None)
97         os.environ.pop('XDG_RUNTIME_DIR', None)
98         with mock.patch('os.path.exists', return_value=True):
99             client = musicpd.MPDClient()
100             self.assertEqual(client.host, '/run/mpd/socket')
101             os.environ['XDG_RUNTIME_DIR'] = '/run/user/1000'
102             client = musicpd.MPDClient()
103             self.assertEqual(client.host, '/run/user/1000/mpd/socket')
104
105         os.environ.pop('MPD_HOST', None)
106         os.environ.pop('MPD_PORT', None)
107         os.environ['XDG_RUNTIME_DIR'] = '/run/user/1000/'
108         with mock.patch('os.path.exists', return_value=True):
109             client = musicpd.MPDClient()
110             self.assertEqual(client.host, '/run/user/1000/mpd/socket')
111
112         # Test MPD_TIMEOUT
113         os.environ.pop('MPD_TIMEOUT', None)
114         client = musicpd.MPDClient()
115         self.assertEqual(client.mpd_timeout, musicpd.CONNECTION_TIMEOUT)
116         os.environ['MPD_TIMEOUT'] = 'garbage'
117         client = musicpd.MPDClient()
118         self.assertEqual(client.mpd_timeout,
119                          musicpd.CONNECTION_TIMEOUT,
120                          'Garbage is silently ignore to use default value')
121         os.environ['MPD_TIMEOUT'] = '42'
122         client = musicpd.MPDClient()
123         self.assertEqual(client.mpd_timeout, 42)
124
125
126 class TestMPDClient(unittest.TestCase):
127
128     longMessage = True
129     # last sync: musicpd 0.6.0 unreleased / Fri Feb 19 15:34:53 CET 2021
130     commands = {
131             # Status Commands
132             'clearerror':         'nothing',
133             'currentsong':        'object',
134             'idle':               'list',
135             'noidle':             None,
136             'status':             'object',
137             'stats':              'object',
138             # Playback Option Commands
139             'consume':            'nothing',
140             'crossfade':          'nothing',
141             'mixrampdb':          'nothing',
142             'mixrampdelay':       'nothing',
143             'random':             'nothing',
144             'repeat':             'nothing',
145             'setvol':             'nothing',
146             'getvol':             'object',
147             'single':             'nothing',
148             'replay_gain_mode':   'nothing',
149             'replay_gain_status': 'item',
150             'volume':             'nothing',
151             # Playback Control Commands
152             'next':               'nothing',
153             'pause':              'nothing',
154             'play':               'nothing',
155             'playid':             'nothing',
156             'previous':           'nothing',
157             'seek':               'nothing',
158             'seekid':             'nothing',
159             'seekcur':            'nothing',
160             'stop':               'nothing',
161             # Queue Commands
162             'add':                'nothing',
163             'addid':              'item',
164             'clear':              'nothing',
165             'delete':             'nothing',
166             'deleteid':           'nothing',
167             'move':               'nothing',
168             'moveid':             'nothing',
169             'playlist':           'playlist',
170             'playlistfind':       'songs',
171             'playlistid':         'songs',
172             'playlistinfo':       'songs',
173             'playlistsearch':     'songs',
174             'plchanges':          'songs',
175             'plchangesposid':     'changes',
176             'prio':               'nothing',
177             'prioid':             'nothing',
178             'rangeid':            'nothing',
179             'shuffle':            'nothing',
180             'swap':               'nothing',
181             'swapid':             'nothing',
182             'addtagid':           'nothing',
183             'cleartagid':         'nothing',
184             # Stored Playlist Commands
185             'listplaylist':       'list',
186             'listplaylistinfo':   'songs',
187             'listplaylists':      'playlists',
188             'load':               'nothing',
189             'playlistadd':        'nothing',
190             'playlistclear':      'nothing',
191             'playlistdelete':     'nothing',
192             'playlistmove':       'nothing',
193             'rename':             'nothing',
194             'rm':                 'nothing',
195             'save':               'nothing',
196             # Database Commands
197             'albumart':           'composite',
198             'count':              'object',
199             'getfingerprint':     'object',
200             'find':               'songs',
201             'findadd':            'nothing',
202             'list':               'list',
203             'listall':            'database',
204             'listallinfo':        'database',
205             'listfiles':          'database',
206             'lsinfo':             'database',
207             'readcomments':       'object',
208             'readpicture':        'composite',
209             'search':             'songs',
210             'searchadd':          'nothing',
211             'searchaddpl':        'nothing',
212             'update':             'item',
213             'rescan':             'item',
214             # Mounts and neighbors
215             'mount':              'nothing',
216             'unmount':            'nothing',
217             'listmounts':         'mounts',
218             'listneighbors':      'neighbors',
219             # Sticker Commands
220             'sticker get':        'item',
221             'sticker set':        'nothing',
222             'sticker delete':     'nothing',
223             'sticker list':       'list',
224             'sticker find':       'songs',
225             # Connection Commands
226             'close':              None,
227             'kill':               None,
228             'password':           'nothing',
229             'ping':               'nothing',
230             'binarylimit':        'nothing',
231             'tagtypes':           'list',
232             'tagtypes disable':   'nothing',
233             'tagtypes enable':    'nothing',
234             'tagtypes clear':     'nothing',
235             'tagtypes all':       'nothing',
236             # Partition Commands
237             'partition':          'nothing',
238             'listpartitions':     'list',
239             'newpartition':       'nothing',
240             'delpartition':       'nothing',
241             'moveoutput':         'nothing',
242             # Audio Output Commands
243             'disableoutput':      'nothing',
244             'enableoutput':       'nothing',
245             'toggleoutput':       'nothing',
246             'outputs':            'outputs',
247             'outputset':          'nothing',
248             # Reflection Commands
249             'config':             'object',
250             'commands':           'list',
251             'notcommands':        'list',
252             'urlhandlers':        'list',
253             'decoders':           'plugins',
254             # Client to Client
255             'subscribe':          'nothing',
256             'unsubscribe':        'nothing',
257             'channels':           'list',
258             'readmessages':       'messages',
259             'sendmessage':        'nothing',
260         }
261
262     def setUp(self):
263         self.socket_patch = mock.patch('musicpd.socket')
264         self.socket_mock = self.socket_patch.start()
265         self.socket_mock.getaddrinfo.return_value = [range(5)]
266
267         self.socket_mock.socket.side_effect = (
268             lambda *a, **kw:
269             # Create a new socket.socket() mock with default attributes,
270             # each time we are calling it back (otherwise, it keeps set
271             # attributes across calls).
272             # That's probably what we want, since reconnecting is like
273             # reinitializing the entire connection, and so, the mock.
274             mock.MagicMock(name='socket.socket'))
275
276         self.client = musicpd.MPDClient()
277         self.client.connect(TEST_MPD_HOST, TEST_MPD_PORT)
278         self.client._sock.reset_mock()
279         self.MPDWillReturn('ACK don\'t forget to setup your mock\n')
280
281     def tearDown(self):
282         self.socket_patch.stop()
283
284     def MPDWillReturn(self, *lines):
285         # Return what the caller wants first, then do as if the socket was
286         # disconnected.
287         self.client._rfile.readline.side_effect = itertools.chain(
288             lines, itertools.repeat(''))
289
290     def MPDWillReturnBinary(self, lines):
291         data = bytearray(b''.join(lines))
292
293         def read(amount):
294             val = bytearray()
295             while amount > 0:
296                 amount -= 1
297                 # _ = data.pop(0)
298                 # print(hex(_))
299                 val.append(data.pop(0))
300             return val
301
302         def readline():
303             val = bytearray()
304             while not val.endswith(b'\x0a'):
305                 val.append(data.pop(0))
306             return val
307         self.client._rbfile.readline.side_effect = readline
308         self.client._rbfile.read.side_effect = read
309
310     def assertMPDReceived(self, *lines):
311         self.client._wfile.write.assert_called_with(*lines)
312
313     def test_metaclass_commands(self):
314         """Controls client has at least commands as last synchronized in
315         TestMPDClient.commands"""
316         for cmd, ret in TestMPDClient.commands.items():
317             self.assertTrue(hasattr(self.client, cmd), msg='cmd "{}" not available!'.format(cmd))
318             if ' ' in cmd:
319                 self.assertTrue(hasattr(self.client, cmd.replace(' ', '_')))
320
321     def test_fetch_nothing(self):
322         self.MPDWillReturn('OK\n')
323         self.assertIsNone(self.client.ping())
324         self.assertMPDReceived('ping\n')
325
326     def test_fetch_list(self):
327         self.MPDWillReturn('OK\n')
328         self.assertIsInstance(self.client.list('album'), list)
329         self.assertMPDReceived('list "album"\n')
330
331     def test_fetch_item(self):
332         self.MPDWillReturn('updating_db: 42\n', 'OK\n')
333         self.assertIsNotNone(self.client.update())
334
335     def test_fetch_object(self):
336         # XXX: _read_objects() doesn't wait for the final OK
337         self.MPDWillReturn('volume: 63\n', 'OK\n')
338         status = self.client.status()
339         self.assertMPDReceived('status\n')
340         self.assertIsInstance(status, dict)
341
342         # XXX: _read_objects() doesn't wait for the final OK
343         self.MPDWillReturn('OK\n')
344         stats = self.client.stats()
345         self.assertMPDReceived('stats\n')
346         self.assertIsInstance(stats, dict)
347
348         output = ['outputid: 0\n',
349                   'outputname: default detected output\n',
350                   'plugin: sndio\n',
351                   'outputenabled: 1\n']
352         self.MPDWillReturn(*output, 'OK\n')
353         outputs = self.client.outputs()
354         self.assertMPDReceived('outputs\n')
355         self.assertIsInstance(outputs, list)
356         self.assertEqual([{'outputid': '0', 'outputname': 'default detected output', 'plugin': 'sndio', 'outputenabled': '1'}], outputs)
357
358     def test_fetch_songs(self):
359         self.MPDWillReturn('file: my-song.ogg\n', 'Pos: 0\n', 'Id: 66\n', 'OK\n')
360         playlist = self.client.playlistinfo()
361
362         self.assertMPDReceived('playlistinfo\n')
363         self.assertIsInstance(playlist, list)
364         self.assertEqual(1, len(playlist))
365         e = playlist[0]
366         self.assertIsInstance(e, dict)
367         self.assertEqual('my-song.ogg', e['file'])
368         self.assertEqual('0', e['pos'])
369         self.assertEqual('66', e['id'])
370
371     def test_send_and_fetch(self):
372         self.MPDWillReturn('volume: 50\n', 'OK\n')
373         result = self.client.send_status()
374         self.assertEqual(None, result)
375         self.assertMPDReceived('status\n')
376
377         status = self.client.fetch_status()
378         self.assertEqual(1, self.client._wfile.write.call_count)
379         self.assertEqual({'volume': '50'}, status)
380
381     def test_iterating(self):
382         self.MPDWillReturn('file: my-song.ogg\n', 'Pos: 0\n', 'Id: 66\n',
383                            'file: my-song.ogg\n', 'Pos: 0\n', 'Id: 66\n', 'OK\n')
384         self.client.iterate = True
385         playlist = self.client.playlistinfo()
386         self.assertMPDReceived('playlistinfo\n')
387         self.assertIsInstance(playlist, types.GeneratorType)
388         self.assertTrue(self.client._iterating)
389         for song in playlist:
390             self.assertRaises(musicpd.IteratingError, self.client.status)
391             self.assertIsInstance(song, dict)
392             self.assertEqual('my-song.ogg', song['file'])
393             self.assertEqual('0', song['pos'])
394             self.assertEqual('66', song['id'])
395         self.assertFalse(self.client._iterating)
396
397     def test_noidle(self):
398         self.MPDWillReturn('OK\n') # nothing changed after idle-ing
399         self.client.send_idle()
400         self.MPDWillReturn('OK\n') # nothing changed after noidle
401         self.assertEqual(self.client.noidle(), [])
402         self.assertMPDReceived('noidle\n')
403         self.MPDWillReturn('volume: 50\n', 'OK\n')
404         self.client.status()
405         self.assertMPDReceived('status\n')
406
407     def test_noidle_while_idle_started_sending(self):
408         self.MPDWillReturn('OK\n') # nothing changed after idle
409         self.client.send_idle()
410         self.MPDWillReturn('CHANGED: player\n', 'OK\n')  # noidle response
411         self.assertEqual(self.client.noidle(), ['player'])
412         self.MPDWillReturn('volume: 50\n', 'OK\n')
413         status = self.client.status()
414         self.assertMPDReceived('status\n')
415         self.assertEqual({'volume': '50'}, status)
416
417     def test_throw_when_calling_noidle_withoutidling(self):
418         self.assertRaises(musicpd.CommandError, self.client.noidle)
419         self.client.send_status()
420         self.assertRaises(musicpd.CommandError, self.client.noidle)
421
422     def test_send_noidle_calls_noidle(self):
423         self.MPDWillReturn('OK\n') # nothing changed after idle
424         self.client.send_idle()
425         self.client.send_noidle()
426         self.assertMPDReceived('noidle\n')
427
428     def test_client_to_client(self):
429         self.MPDWillReturn('OK\n')
430         self.assertIsNone(self.client.subscribe("monty"))
431         self.assertMPDReceived('subscribe "monty"\n')
432
433         self.MPDWillReturn('channel: monty\n', 'OK\n')
434         channels = self.client.channels()
435         self.assertMPDReceived('channels\n')
436         self.assertEqual(['monty'], channels)
437
438         self.MPDWillReturn('OK\n')
439         self.assertIsNone(self.client.sendmessage('monty', 'SPAM'))
440         self.assertMPDReceived('sendmessage "monty" "SPAM"\n')
441
442         self.MPDWillReturn('channel: monty\n', 'message: SPAM\n', 'OK\n')
443         msg = self.client.readmessages()
444         self.assertMPDReceived('readmessages\n')
445         self.assertEqual(msg, [{'channel': 'monty', 'message': 'SPAM'}])
446
447         self.MPDWillReturn('OK\n')
448         self.assertIsNone(self.client.unsubscribe('monty'))
449         self.assertMPDReceived('unsubscribe "monty"\n')
450
451         self.MPDWillReturn('OK\n')
452         channels = self.client.channels()
453         self.assertMPDReceived('channels\n')
454         self.assertEqual([], channels)
455
456     def test_ranges_in_command_args(self):
457         self.MPDWillReturn('OK\n')
458         self.client.playlistinfo((10,))
459         self.assertMPDReceived('playlistinfo 10:\n')
460
461         self.MPDWillReturn('OK\n')
462         self.client.playlistinfo(('10',))
463         self.assertMPDReceived('playlistinfo 10:\n')
464
465         self.MPDWillReturn('OK\n')
466         self.client.playlistinfo((10, 12))
467         self.assertMPDReceived('playlistinfo 10:12\n')
468
469         self.MPDWillReturn('OK\n')
470         self.client.rangeid(())
471         self.assertMPDReceived('rangeid :\n')
472
473         for arg in [(10, 't'), (10, 1, 1), (None,1)]:
474             self.MPDWillReturn('OK\n')
475             with self.assertRaises(musicpd.CommandError):
476                 self.client.playlistinfo(arg)
477
478     def test_numbers_as_command_args(self):
479         self.MPDWillReturn('OK\n')
480         self.client.find('file', 1)
481         self.assertMPDReceived('find "file" "1"\n')
482
483     def test_commands_without_callbacks(self):
484         self.MPDWillReturn('\n')
485         self.client.close()
486         self.assertMPDReceived('close\n')
487
488         # XXX: what are we testing here?
489         self.client._reset()
490         self.client.connect(TEST_MPD_HOST, TEST_MPD_PORT)
491
492     def test_connection_lost(self):
493         # Simulate a connection lost: the socket returns empty strings
494         self.MPDWillReturn('')
495
496         with self.assertRaises(musicpd.ConnectionError):
497             self.client.status()
498
499         # consistent behaviour
500         # solves <https://github.com/Mic92/python-mpd2/issues/11> (also present
501         # in python-mpd)
502         with self.assertRaises(musicpd.ConnectionError):
503             self.client.status()
504
505         self.assertIs(self.client._sock, None)
506
507     def test_parse_sticker_get_no_sticker(self):
508         self.MPDWillReturn('ACK [50@0] {sticker} no such sticker\n')
509         self.assertRaises(musicpd.CommandError,
510                           self.client.sticker_get, 'song', 'baz', 'foo')
511
512     def test_parse_sticker_list(self):
513         self.MPDWillReturn('sticker: foo=bar\n', 'sticker: lom=bok\n', 'OK\n')
514         res = self.client.sticker_list('song', 'baz')
515         self.assertEqual(['foo=bar', 'lom=bok'], res)
516
517         # Even with only one sticker, we get a dict
518         self.MPDWillReturn('sticker: foo=bar\n', 'OK\n')
519         res = self.client.sticker_list('song', 'baz')
520         self.assertEqual(['foo=bar'], res)
521
522     def test_albumart(self):
523         # here is a 34 bytes long data
524         data = bytes('\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01'
525                      '\x00\x01\x00\x00\xff\xdb\x00C\x00\x05\x03\x04',
526                      encoding='utf8')
527         read_lines = [b'size: 42\nbinary: 34\n', data, b'\nOK\n']
528         self.MPDWillReturnBinary(read_lines)
529         # Reading albumart / offset 0 should return the data
530         res = self.client.albumart('muse/Raised Fist/2002-Dedication/', 0)
531         self.assertEqual(res.get('data'), data)
532
533     def test_reading_binary(self):
534         # readpicture when there are no picture returns empty object
535         self.MPDWillReturnBinary([b'OK\n'])
536         res = self.client.readpicture('muse/Raised Fist/2002-Dedication/', 0)
537         self.assertEqual(res, {})
538
539     def test_command_list(self):
540         self.MPDWillReturn('updating_db: 42\n',
541                            f'{musicpd.NEXT}\n',
542                            'repeat: 0\n',
543                            'random: 0\n',
544                            f'{musicpd.NEXT}\n',
545                            f'{musicpd.NEXT}\n',
546                            'OK\n')
547         self.client.command_list_ok_begin()
548         self.client.update()
549         self.client.status()
550         self.client.repeat(1)
551         self.client.command_list_end()
552         self.assertMPDReceived('command_list_end\n')
553
554     def test_two_word_commands(self):
555         self.MPDWillReturn('OK\n')
556         self.client.tagtypes_clear()
557         self.assertMPDReceived('tagtypes clear\n')
558         self.MPDWillReturn('OK\n')
559         with self.assertRaises(AttributeError):
560             self.client.foo_bar()
561
562 class testConnection(unittest.TestCase):
563
564     def test_exposing_fileno(self):
565         with mock.patch('musicpd.socket') as socket_mock:
566             sock = mock.MagicMock(name='socket')
567             socket_mock.socket.return_value = sock
568             cli = musicpd.MPDClient()
569             cli.connect()
570             cli.fileno()
571             cli._sock.fileno.assert_called_with()
572
573     def test_connect_abstract(self):
574         os.environ['MPD_HOST'] = '@abstract'
575         with mock.patch('musicpd.socket') as socket_mock:
576             sock = mock.MagicMock(name='socket')
577             socket_mock.socket.return_value = sock
578             cli = musicpd.MPDClient()
579             cli.connect()
580             sock.connect.assert_called_with('\0abstract')
581
582     def test_connect_unix(self):
583         os.environ['MPD_HOST'] = '/run/mpd/socket'
584         with mock.patch('musicpd.socket') as socket_mock:
585             sock = mock.MagicMock(name='socket')
586             socket_mock.socket.return_value = sock
587             cli = musicpd.MPDClient()
588             cli.connect()
589             sock.connect.assert_called_with('/run/mpd/socket')
590
591     def test_sockettimeout(self):
592         with mock.patch('musicpd.socket') as socket_mock:
593             sock = mock.MagicMock(name='socket')
594             socket_mock.socket.return_value = sock
595             cli = musicpd.MPDClient()
596             # Default is no socket timeout
597             cli.connect()
598             sock.settimeout.assert_called_with(None)
599             cli.disconnect()
600             # set a socket timeout before connection
601             cli.socket_timeout = 10
602             cli.connect()
603             sock.settimeout.assert_called_with(10)
604             # Set socket timeout while already connected
605             cli.socket_timeout = 42
606             sock.settimeout.assert_called_with(42)
607             # set a socket timeout using str
608             cli.socket_timeout = '10'
609             sock.settimeout.assert_called_with(10)
610             # Set socket timeout to None
611             cli.socket_timeout = None
612             sock.settimeout.assert_called_with(None)
613             # Set socket timeout Raises Exception
614             with self.assertRaises(ValueError):
615                 cli.socket_timeout = 'foo'
616             with self.assertRaises(ValueError, msg='socket_timeout expects a non zero positive integer'):
617                 cli.socket_timeout = '0'
618             with self.assertRaises(ValueError, msg='socket_timeout expects a non zero positive integer'):
619                 cli.socket_timeout = '-1'
620
621 class testException(unittest.TestCase):
622
623     def test_CommandError_on_newline(self):
624         os.environ['MPD_HOST'] = '/run/mpd/socket'
625         with mock.patch('musicpd.socket') as socket_mock:
626             sock = mock.MagicMock(name='socket')
627             socket_mock.socket.return_value = sock
628             cli = musicpd.MPDClient()
629             cli.connect()
630             with self.assertRaises(musicpd.CommandError):
631                 cli.find('(album == "foo\nbar")')
632
633 class testContextManager(unittest.TestCase):
634
635     def test_enter_exit(self):
636         os.environ['MPD_HOST'] = '@abstract'
637         with mock.patch('musicpd.socket') as socket_mock:
638             sock = mock.MagicMock(name='socket')
639             socket_mock.socket.return_value = sock
640             cli = musicpd.MPDClient()
641             with cli as c:
642                 sock.connect.assert_called_with('\0abstract')
643                 sock.close.assert_not_called()
644             sock.close.assert_called()
645
646 class testRange(unittest.TestCase):
647
648     def test_range(self):
649         tests = [
650                 ((),          ':'),
651                 ((None,None), ':'),
652                 (('',''),     ':'),
653                 (('',),       ':'),
654                 ((42,42),     '42:42'),
655                 ((42,),       '42:'),
656                 (('42',),     '42:'),
657                 (('42',None), '42:'),
658                 (('42',''),   '42:'),
659         ]
660         for tpl, result in tests:
661             self.assertEqual(str(musicpd.Range(tpl)), result)
662         with self.assertRaises(musicpd.CommandError):
663             #CommandError: Integer expected to start the range: (None, 42)
664             musicpd.Range((None,'42'))
665         with self.assertRaises(musicpd.CommandError):
666             # CommandError: Not an integer: "foo"
667             musicpd.Range(('foo',))
668         with self.assertRaises(musicpd.CommandError):
669             # CommandError: Wrong range: 42 > 41
670             musicpd.Range(('42',41))
671         with self.assertRaises(musicpd.CommandError):
672             # CommandError: Wrong range: 42 > 41
673             musicpd.Range(('42','42','42'))
674
675
676 if __name__ == '__main__':
677     unittest.main()