3 # SPDX-FileCopyrightText: 2012-2024 kaliko <kaliko@azylum.org>
4 # SPDX-License-Identifier: LGPL-3.0-or-later
5 # pylint: disable=missing-docstring
7 Test suite highly borrowed^Wsteal from python-mpd2 [0] project.
9 [0] https://github.com/Mic92/python-mpd2
24 # show deprecation warnings
25 warnings.simplefilter('default')
28 TEST_MPD_HOST, TEST_MPD_PORT = ('example.com', 10000)
31 class TestEnvVar(unittest.TestCase):
33 def test_envvar(self):
34 # mock "os.path.exists" here to ensure there are no socket in
35 # XDG_RUNTIME_DIR/mpd or /run/mpd since with test defaults fallbacks
37 # * neither MPD_HOST nor XDG_RUNTIME_DIR are not set
38 # * /run/mpd does not expose a socket
39 with mock.patch('os.path.exists', return_value=False):
40 os.environ.pop('MPD_HOST', None)
41 os.environ.pop('MPD_PORT', None)
42 client = musicpd.MPDClient()
43 self.assertEqual(client.host, 'localhost')
44 self.assertEqual(client.port, '6600')
46 os.environ.pop('MPD_HOST', None)
47 os.environ['MPD_PORT'] = '6666'
48 client = musicpd.MPDClient()
49 self.assertEqual(client.pwd, None)
50 self.assertEqual(client.host, 'localhost')
51 self.assertEqual(client.port, '6666')
53 # Test password extraction
54 os.environ['MPD_HOST'] = 'pa55w04d@example.org'
55 client = musicpd.MPDClient()
56 self.assertEqual(client.pwd, 'pa55w04d')
57 self.assertEqual(client.host, 'example.org')
60 os.environ['MPD_HOST'] = 'example.org'
61 client = musicpd.MPDClient()
62 self.assertFalse(client.pwd)
63 self.assertEqual(client.host, 'example.org')
65 # Test password extraction (no host)
66 os.environ['MPD_HOST'] = 'pa55w04d@'
67 with mock.patch('os.path.exists', return_value=False):
68 client = musicpd.MPDClient()
69 self.assertEqual(client.pwd, 'pa55w04d')
70 self.assertEqual(client.host, 'localhost')
72 # Test badly formatted MPD_HOST
73 os.environ['MPD_HOST'] = '@'
74 with mock.patch('os.path.exists', return_value=False):
75 client = musicpd.MPDClient()
76 self.assertEqual(client.pwd, None)
77 self.assertEqual(client.host, 'localhost')
79 # Test unix socket extraction
80 os.environ['MPD_HOST'] = 'pa55w04d@/unix/sock'
81 client = musicpd.MPDClient()
82 self.assertEqual(client.host, '/unix/sock')
84 # Test plain abstract socket extraction
85 os.environ['MPD_HOST'] = '@abstract'
86 client = musicpd.MPDClient()
87 self.assertEqual(client.host, '@abstract')
89 # Test password and abstract socket extraction
90 os.environ['MPD_HOST'] = 'pass@@abstract'
91 client = musicpd.MPDClient()
92 self.assertEqual(client.pwd, 'pass')
93 self.assertEqual(client.host, '@abstract')
95 # Test unix socket fallback
96 os.environ.pop('MPD_HOST', None)
97 os.environ.pop('MPD_PORT', None)
98 os.environ.pop('XDG_RUNTIME_DIR', None)
99 with mock.patch('os.path.exists', return_value=True):
100 client = musicpd.MPDClient()
101 self.assertEqual(client.host, '/run/mpd/socket')
102 os.environ['XDG_RUNTIME_DIR'] = '/run/user/1000'
103 client = musicpd.MPDClient()
104 self.assertEqual(client.host, '/run/user/1000/mpd/socket')
106 os.environ.pop('MPD_HOST', None)
107 os.environ.pop('MPD_PORT', None)
108 os.environ['XDG_RUNTIME_DIR'] = '/run/user/1000/'
109 with mock.patch('os.path.exists', return_value=True):
110 client = musicpd.MPDClient()
111 self.assertEqual(client.host, '/run/user/1000/mpd/socket')
114 os.environ.pop('MPD_TIMEOUT', None)
115 client = musicpd.MPDClient()
116 self.assertEqual(client.mpd_timeout, musicpd.CONNECTION_TIMEOUT)
117 os.environ['MPD_TIMEOUT'] = 'garbage'
118 client = musicpd.MPDClient()
119 self.assertEqual(client.mpd_timeout,
120 musicpd.CONNECTION_TIMEOUT,
121 'Garbage is silently ignore to use default value')
122 os.environ['MPD_TIMEOUT'] = '42'
123 client = musicpd.MPDClient()
124 self.assertEqual(client.mpd_timeout, 42)
127 class TestMPDClient(unittest.TestCase):
130 # last sync: musicpd 0.6.0 unreleased / Fri Feb 19 15:34:53 CET 2021
133 'clearerror': 'nothing',
134 'currentsong': 'object',
139 # Playback Option Commands
140 'consume': 'nothing',
141 'crossfade': 'nothing',
142 'mixrampdb': 'nothing',
143 'mixrampdelay': 'nothing',
149 'replay_gain_mode': 'nothing',
150 'replay_gain_status': 'item',
152 # Playback Control Commands
157 'previous': 'nothing',
160 'seekcur': 'nothing',
167 'deleteid': 'nothing',
170 'playlist': 'playlist',
171 'playlistfind': 'songs',
172 'playlistid': 'songs',
173 'playlistinfo': 'songs',
174 'playlistsearch': 'songs',
175 'plchanges': 'songs',
176 'plchangesposid': 'changes',
179 'rangeid': 'nothing',
180 'shuffle': 'nothing',
183 'addtagid': 'nothing',
184 'cleartagid': 'nothing',
185 # Stored Playlist Commands
186 'listplaylist': 'list',
187 'listplaylistinfo': 'songs',
188 'listplaylists': 'playlists',
190 'playlistadd': 'nothing',
191 'playlistclear': 'nothing',
192 'playlistdelete': 'nothing',
193 'playlistmove': 'nothing',
198 'albumart': 'composite',
200 'getfingerprint': 'object',
202 'findadd': 'nothing',
204 'listall': 'database',
205 'listallinfo': 'database',
206 'listfiles': 'database',
207 'lsinfo': 'database',
208 'readcomments': 'object',
209 'readpicture': 'composite',
211 'searchadd': 'nothing',
212 'searchaddpl': 'nothing',
215 # Mounts and neighbors
217 'unmount': 'nothing',
218 'listmounts': 'mounts',
219 'listneighbors': 'neighbors',
221 'sticker get': 'item',
222 'sticker set': 'nothing',
223 'sticker delete': 'nothing',
224 'sticker list': 'list',
225 'sticker find': 'songs',
226 # Connection Commands
229 'password': 'nothing',
231 'binarylimit': 'nothing',
233 'tagtypes disable': 'nothing',
234 'tagtypes enable': 'nothing',
235 'tagtypes clear': 'nothing',
236 'tagtypes all': 'nothing',
238 'partition': 'nothing',
239 'listpartitions': 'list',
240 'newpartition': 'nothing',
241 'delpartition': 'nothing',
242 'moveoutput': 'nothing',
243 # Audio Output Commands
244 'disableoutput': 'nothing',
245 'enableoutput': 'nothing',
246 'toggleoutput': 'nothing',
247 'outputs': 'outputs',
248 'outputset': 'nothing',
249 # Reflection Commands
252 'notcommands': 'list',
253 'urlhandlers': 'list',
254 'decoders': 'plugins',
256 'subscribe': 'nothing',
257 'unsubscribe': 'nothing',
259 'readmessages': 'messages',
260 'sendmessage': 'nothing',
264 self.socket_patch = mock.patch('musicpd.socket')
265 self.socket_mock = self.socket_patch.start()
266 self.socket_mock.getaddrinfo.return_value = [range(5)]
268 self.socket_mock.socket.side_effect = (
270 # Create a new socket.socket() mock with default attributes,
271 # each time we are calling it back (otherwise, it keeps set
272 # attributes across calls).
273 # That's probably what we want, since reconnecting is like
274 # reinitializing the entire connection, and so, the mock.
275 mock.MagicMock(name='socket.socket'))
277 self.client = musicpd.MPDClient()
278 self.client.connect(TEST_MPD_HOST, TEST_MPD_PORT)
279 self.client._sock.reset_mock()
280 self.MPDWillReturn('ACK don\'t forget to setup your mock\n')
283 self.socket_patch.stop()
285 def MPDWillReturn(self, *lines):
286 # Return what the caller wants first, then do as if the socket was
288 self.client._rfile.readline.side_effect = itertools.chain(
289 lines, itertools.repeat(''))
291 def MPDWillReturnBinary(self, lines):
292 data = bytearray(b''.join(lines))
300 val.append(data.pop(0))
305 while not val.endswith(b'\x0a'):
306 val.append(data.pop(0))
308 self.client._rbfile.readline.side_effect = readline
309 self.client._rbfile.read.side_effect = read
311 def assertMPDReceived(self, *lines):
312 self.client._wfile.write.assert_called_with(*lines)
314 def test_metaclass_commands(self):
315 """Controls client has at least commands as last synchronized in
316 TestMPDClient.commands"""
317 for cmd, ret in TestMPDClient.commands.items():
318 self.assertTrue(hasattr(self.client, cmd), msg='cmd "{}" not available!'.format(cmd))
320 self.assertTrue(hasattr(self.client, cmd.replace(' ', '_')))
322 def test_fetch_nothing(self):
323 self.MPDWillReturn('OK\n')
324 self.assertIsNone(self.client.ping())
325 self.assertMPDReceived('ping\n')
327 def test_fetch_list(self):
328 self.MPDWillReturn('OK\n')
329 self.assertIsInstance(self.client.list('album'), list)
330 self.assertMPDReceived('list "album"\n')
332 def test_fetch_item(self):
333 self.MPDWillReturn('updating_db: 42\n', 'OK\n')
334 self.assertIsNotNone(self.client.update())
336 def test_fetch_object(self):
337 # XXX: _read_objects() doesn't wait for the final OK
338 self.MPDWillReturn('volume: 63\n', 'OK\n')
339 status = self.client.status()
340 self.assertMPDReceived('status\n')
341 self.assertIsInstance(status, dict)
343 # XXX: _read_objects() doesn't wait for the final OK
344 self.MPDWillReturn('OK\n')
345 stats = self.client.stats()
346 self.assertMPDReceived('stats\n')
347 self.assertIsInstance(stats, dict)
349 output = ['outputid: 0\n',
350 'outputname: default detected output\n',
352 'outputenabled: 1\n']
353 self.MPDWillReturn(*output, 'OK\n')
354 outputs = self.client.outputs()
355 self.assertMPDReceived('outputs\n')
356 self.assertIsInstance(outputs, list)
357 self.assertEqual([{'outputid': '0', 'outputname': 'default detected output', 'plugin': 'sndio', 'outputenabled': '1'}], outputs)
359 def test_fetch_songs(self):
360 self.MPDWillReturn('file: my-song.ogg\n', 'Pos: 0\n', 'Id: 66\n', 'OK\n')
361 playlist = self.client.playlistinfo()
363 self.assertMPDReceived('playlistinfo\n')
364 self.assertIsInstance(playlist, list)
365 self.assertEqual(1, len(playlist))
367 self.assertIsInstance(e, dict)
368 self.assertEqual('my-song.ogg', e['file'])
369 self.assertEqual('0', e['pos'])
370 self.assertEqual('66', e['id'])
372 def test_send_and_fetch(self):
373 self.MPDWillReturn('volume: 50\n', 'OK\n')
374 result = self.client.send_status()
375 self.assertEqual(None, result)
376 self.assertMPDReceived('status\n')
378 status = self.client.fetch_status()
379 self.assertEqual(1, self.client._wfile.write.call_count)
380 self.assertEqual({'volume': '50'}, status)
382 def test_iterating(self):
383 self.MPDWillReturn('file: my-song.ogg\n', 'Pos: 0\n', 'Id: 66\n',
384 'file: my-song.ogg\n', 'Pos: 0\n', 'Id: 66\n', 'OK\n')
385 self.client.iterate = True
386 playlist = self.client.playlistinfo()
387 self.assertMPDReceived('playlistinfo\n')
388 self.assertIsInstance(playlist, types.GeneratorType)
389 self.assertTrue(self.client._iterating)
390 for song in playlist:
391 self.assertRaises(musicpd.IteratingError, self.client.status)
392 self.assertIsInstance(song, dict)
393 self.assertEqual('my-song.ogg', song['file'])
394 self.assertEqual('0', song['pos'])
395 self.assertEqual('66', song['id'])
396 self.assertFalse(self.client._iterating)
398 def test_noidle(self):
399 self.MPDWillReturn('OK\n') # nothing changed after idle-ing
400 self.client.send_idle()
401 self.MPDWillReturn('OK\n') # nothing changed after noidle
402 self.assertEqual(self.client.noidle(), [])
403 self.assertMPDReceived('noidle\n')
404 self.MPDWillReturn('volume: 50\n', 'OK\n')
406 self.assertMPDReceived('status\n')
408 def test_noidle_while_idle_started_sending(self):
409 self.MPDWillReturn('OK\n') # nothing changed after idle
410 self.client.send_idle()
411 self.MPDWillReturn('CHANGED: player\n', 'OK\n') # noidle response
412 self.assertEqual(self.client.noidle(), ['player'])
413 self.MPDWillReturn('volume: 50\n', 'OK\n')
414 status = self.client.status()
415 self.assertMPDReceived('status\n')
416 self.assertEqual({'volume': '50'}, status)
418 def test_throw_when_calling_noidle_withoutidling(self):
419 self.assertRaises(musicpd.CommandError, self.client.noidle)
420 self.client.send_status()
421 self.assertRaises(musicpd.CommandError, self.client.noidle)
423 def test_send_noidle_calls_noidle(self):
424 self.MPDWillReturn('OK\n') # nothing changed after idle
425 self.client.send_idle()
426 self.client.send_noidle()
427 self.assertMPDReceived('noidle\n')
429 def test_client_to_client(self):
430 self.MPDWillReturn('OK\n')
431 self.assertIsNone(self.client.subscribe("monty"))
432 self.assertMPDReceived('subscribe "monty"\n')
434 self.MPDWillReturn('channel: monty\n', 'OK\n')
435 channels = self.client.channels()
436 self.assertMPDReceived('channels\n')
437 self.assertEqual(['monty'], channels)
439 self.MPDWillReturn('OK\n')
440 self.assertIsNone(self.client.sendmessage('monty', 'SPAM'))
441 self.assertMPDReceived('sendmessage "monty" "SPAM"\n')
443 self.MPDWillReturn('channel: monty\n', 'message: SPAM\n', 'OK\n')
444 msg = self.client.readmessages()
445 self.assertMPDReceived('readmessages\n')
446 self.assertEqual(msg, [{'channel': 'monty', 'message': 'SPAM'}])
448 self.MPDWillReturn('OK\n')
449 self.assertIsNone(self.client.unsubscribe('monty'))
450 self.assertMPDReceived('unsubscribe "monty"\n')
452 self.MPDWillReturn('OK\n')
453 channels = self.client.channels()
454 self.assertMPDReceived('channels\n')
455 self.assertEqual([], channels)
457 def test_ranges_in_command_args(self):
458 self.MPDWillReturn('OK\n')
459 self.client.playlistinfo((10,))
460 self.assertMPDReceived('playlistinfo 10:\n')
462 self.MPDWillReturn('OK\n')
463 self.client.playlistinfo(('10',))
464 self.assertMPDReceived('playlistinfo 10:\n')
466 self.MPDWillReturn('OK\n')
467 self.client.playlistinfo((10, 12))
468 self.assertMPDReceived('playlistinfo 10:12\n')
470 self.MPDWillReturn('OK\n')
471 self.client.rangeid(())
472 self.assertMPDReceived('rangeid :\n')
474 for arg in [(10, 't'), (10, 1, 1), (None,1)]:
475 self.MPDWillReturn('OK\n')
476 with self.assertRaises(musicpd.CommandError):
477 self.client.playlistinfo(arg)
479 def test_numbers_as_command_args(self):
480 self.MPDWillReturn('OK\n')
481 self.client.find('file', 1)
482 self.assertMPDReceived('find "file" "1"\n')
484 def test_commands_without_callbacks(self):
485 self.MPDWillReturn('\n')
487 self.assertMPDReceived('close\n')
489 # XXX: what are we testing here?
491 self.client.connect(TEST_MPD_HOST, TEST_MPD_PORT)
493 def test_connection_lost(self):
494 # Simulate a connection lost: the socket returns empty strings
495 self.MPDWillReturn('')
497 with self.assertRaises(musicpd.ConnectionError):
500 # consistent behaviour
501 # solves <https://github.com/Mic92/python-mpd2/issues/11> (also present
503 with self.assertRaises(musicpd.ConnectionError):
506 self.assertIs(self.client._sock, None)
508 def test_parse_sticker_get_no_sticker(self):
509 self.MPDWillReturn('ACK [50@0] {sticker} no such sticker\n')
510 self.assertRaises(musicpd.CommandError,
511 self.client.sticker_get, 'song', 'baz', 'foo')
513 def test_parse_sticker_list(self):
514 self.MPDWillReturn('sticker: foo=bar\n', 'sticker: lom=bok\n', 'OK\n')
515 res = self.client.sticker_list('song', 'baz')
516 self.assertEqual(['foo=bar', 'lom=bok'], res)
518 # Even with only one sticker, we get a dict
519 self.MPDWillReturn('sticker: foo=bar\n', 'OK\n')
520 res = self.client.sticker_list('song', 'baz')
521 self.assertEqual(['foo=bar'], res)
523 def test_albumart(self):
524 # here is a 34 bytes long data
525 data = bytes('\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01'
526 '\x00\x01\x00\x00\xff\xdb\x00C\x00\x05\x03\x04',
528 read_lines = [b'size: 42\nbinary: 34\n', data, b'\nOK\n']
529 self.MPDWillReturnBinary(read_lines)
530 # Reading albumart / offset 0 should return the data
531 res = self.client.albumart('muse/Raised Fist/2002-Dedication/', 0)
532 self.assertEqual(res.get('data'), data)
534 def test_reading_binary(self):
535 # readpicture when there are no picture returns empty object
536 self.MPDWillReturnBinary([b'OK\n'])
537 res = self.client.readpicture('muse/Raised Fist/2002-Dedication/', 0)
538 self.assertEqual(res, {})
540 def test_command_list(self):
541 self.MPDWillReturn('updating_db: 42\n',
548 self.client.command_list_ok_begin()
551 self.client.repeat(1)
552 self.client.command_list_end()
553 self.assertMPDReceived('command_list_end\n')
555 def test_two_word_commands(self):
556 self.MPDWillReturn('OK\n')
557 self.client.tagtypes_clear()
558 self.assertMPDReceived('tagtypes clear\n')
559 self.MPDWillReturn('OK\n')
560 with self.assertRaises(AttributeError):
561 self.client.foo_bar()
563 class TestConnection(unittest.TestCase):
565 def test_exposing_fileno(self):
566 with mock.patch('musicpd.socket') as socket_mock:
567 sock = mock.MagicMock(name='socket')
568 socket_mock.socket.return_value = sock
569 cli = musicpd.MPDClient()
572 cli._sock.fileno.assert_called_with()
574 def test_connect_abstract(self):
575 os.environ['MPD_HOST'] = '@abstract'
576 with mock.patch('musicpd.socket') as socket_mock:
577 sock = mock.MagicMock(name='socket')
578 socket_mock.socket.return_value = sock
579 cli = musicpd.MPDClient()
581 sock.connect.assert_called_with('\0abstract')
583 def test_connect_unix(self):
584 os.environ['MPD_HOST'] = '/run/mpd/socket'
585 with mock.patch('musicpd.socket') as socket_mock:
586 sock = mock.MagicMock(name='socket')
587 socket_mock.socket.return_value = sock
588 cli = musicpd.MPDClient()
590 sock.connect.assert_called_with('/run/mpd/socket')
592 def test_sockettimeout(self):
593 with mock.patch('musicpd.socket') as socket_mock:
594 sock = mock.MagicMock(name='socket')
595 socket_mock.socket.return_value = sock
596 cli = musicpd.MPDClient()
597 # Default is no socket timeout
599 sock.settimeout.assert_called_with(None)
601 # set a socket timeout before connection
602 cli.socket_timeout = 10
604 sock.settimeout.assert_called_with(10)
605 # Set socket timeout while already connected
606 cli.socket_timeout = 42
607 sock.settimeout.assert_called_with(42)
608 # set a socket timeout using str
609 cli.socket_timeout = '10'
610 sock.settimeout.assert_called_with(10)
611 # Set socket timeout to None
612 cli.socket_timeout = None
613 sock.settimeout.assert_called_with(None)
614 # Set socket timeout Raises Exception
615 with self.assertRaises(ValueError):
616 cli.socket_timeout = 'foo'
617 with self.assertRaises(ValueError,
618 msg='socket_timeout expects a non zero positive integer'):
619 cli.socket_timeout = '0'
620 with self.assertRaises(ValueError,
621 msg='socket_timeout expects a non zero positive integer'):
622 cli.socket_timeout = '-1'
625 class TestConnectionError(unittest.TestCase):
627 @mock.patch('socket.socket')
628 def test_connect_unix(self, socket_mock):
629 """Unix socket socket.error should raise a musicpd.ConnectionError"""
630 mocked_socket = socket_mock.return_value
631 mocked_socket.connect.side_effect = musicpd.socket.error(42, 'err 42')
632 os.environ['MPD_HOST'] = '/run/mpd/socket'
633 cli = musicpd.MPDClient()
634 with self.assertRaises(musicpd.ConnectionError) as cme:
636 self.assertEqual('[Errno 42] err 42', str(cme.exception))
638 def test_non_available_unix_socket(self):
639 delattr(musicpd.socket, 'AF_UNIX')
640 os.environ['MPD_HOST'] = '/run/mpd/socket'
641 cli = musicpd.MPDClient()
642 with self.assertRaises(musicpd.ConnectionError) as cme:
644 self.assertEqual('Unix domain sockets not supported on this platform',
647 @mock.patch('socket.getaddrinfo')
648 def test_connect_tcp_getaddrinfo(self, gai_mock):
649 """TCP socket.gaierror should raise a musicpd.ConnectionError"""
650 gai_mock.side_effect = musicpd.socket.error(42, 'gaierr 42')
651 cli = musicpd.MPDClient()
652 with self.assertRaises(musicpd.ConnectionError) as cme:
653 cli.connect(host=TEST_MPD_HOST)
654 self.assertEqual('[Errno 42] gaierr 42', str(cme.exception))
656 @mock.patch('socket.getaddrinfo')
657 @mock.patch('socket.socket')
658 def test_connect_tcp_connect(self, socket_mock, gai_mock):
659 """A socket.error should raise a musicpd.ConnectionError
660 Mocking getaddrinfo to prevent network access (DNS)
662 gai_mock.return_value = [range(5)]
663 mocked_socket = socket_mock.return_value
664 mocked_socket.connect.side_effect = musicpd.socket.error(42, 'tcp conn err 42')
665 cli = musicpd.MPDClient()
666 with self.assertRaises(musicpd.ConnectionError) as cme:
667 cli.connect(host=TEST_MPD_HOST)
668 self.assertEqual('[Errno 42] tcp conn err 42', str(cme.exception))
670 @mock.patch('socket.getaddrinfo')
671 def test_connect_tcp_connect_empty_gai(self, gai_mock):
672 """An empty getaddrinfo should raise a musicpd.ConnectionError"""
673 gai_mock.return_value = []
674 cli = musicpd.MPDClient()
675 with self.assertRaises(musicpd.ConnectionError) as cme:
676 cli.connect(host=TEST_MPD_HOST)
677 self.assertEqual('getaddrinfo returns an empty list', str(cme.exception))
680 class TestCommandErrorException(unittest.TestCase):
682 def test_error_on_newline(self):
683 os.environ['MPD_HOST'] = '/run/mpd/socket'
684 with mock.patch('musicpd.socket') as socket_mock:
685 sock = mock.MagicMock(name='socket')
686 socket_mock.socket.return_value = sock
687 cli = musicpd.MPDClient()
689 with self.assertRaises(musicpd.CommandError):
690 cli.find('(album == "foo\nbar")')
692 class testContextManager(unittest.TestCase):
694 def test_enter_exit(self):
695 os.environ['MPD_HOST'] = '@abstract'
696 with mock.patch('musicpd.socket') as socket_mock:
697 sock = mock.MagicMock(name='socket')
698 socket_mock.socket.return_value = sock
699 cli = musicpd.MPDClient()
701 sock.connect.assert_called_with('\0abstract')
702 sock.close.assert_not_called()
703 sock.close.assert_called()
705 class testRange(unittest.TestCase):
707 def test_range(self):
716 (('42',None), '42:'),
719 for tpl, result in tests:
720 self.assertEqual(str(musicpd.Range(tpl)), result)
721 with self.assertRaises(musicpd.CommandError):
722 #CommandError: Integer expected to start the range: (None, 42)
723 musicpd.Range((None,'42'))
724 with self.assertRaises(musicpd.CommandError):
725 # CommandError: Not an integer: "foo"
726 musicpd.Range(('foo',))
727 with self.assertRaises(musicpd.CommandError):
728 # CommandError: Wrong range: 42 > 41
729 musicpd.Range(('42',41))
730 with self.assertRaises(musicpd.CommandError):
731 # CommandError: Wrong range: 42 > 41
732 musicpd.Range(('42','42','42'))
735 if __name__ == '__main__':