From 0ccb5dec23e32747eb4e59fb8742e930a36e2601 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 26 Jun 2011 17:37:22 +0200 Subject: [PATCH 01/43] Add debug tool that inspects MPD's idle behaviour --- tools/idle.py | 201 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 tools/idle.py diff --git a/tools/idle.py b/tools/idle.py new file mode 100644 index 00000000..aa56dce2 --- /dev/null +++ b/tools/idle.py @@ -0,0 +1,201 @@ +#! /usr/bin/env python + +# This script is helper to systematicly test the behaviour of MPD's idle +# command. It is simply provided as a quick hack, expect nothing more. + +import logging +import pprint +import socket + +host = '' +port = 6601 + +url = "13 - a-ha - White Canvas.mp3" +artist = "a-ha" + +data = {'id': None, 'id2': None, 'url': url, 'artist': artist} + +# Commands to run before test requests to coerce MPD into right state +setup_requests = [ + 'clear', + 'add "%(url)s"', + 'add "%(url)s"', + 'add "%(url)s"', + 'play', +# 'pause', # Uncomment to test paused idle behaviour +# 'stop', # Uncomment to test stopped idle behaviour +] + +# List of commands to test for idle behaviour. Ordering of list is important in +# order to keep MPD state as intended. Commands that are obviously +# informational only or "harmfull" have been excluded. +test_requests = [ + 'add "%(url)s"', + 'addid "%(url)s" "1"', + 'clear', +# 'clearerror', +# 'close', +# 'commands', + 'consume "1"', + 'consume "0"', +# 'count', + 'crossfade "1"', + 'crossfade "0"', +# 'currentsong', +# 'delete "1:2"', + 'delete "0"', + 'deleteid "%(id)s"', + 'disableoutput "0"', + 'enableoutput "0"', +# 'find', +# 'findadd "artist" "%(artist)s"', +# 'idle', +# 'kill', +# 'list', +# 'listall', +# 'listallinfo', +# 'listplaylist', +# 'listplaylistinfo', +# 'listplaylists', +# 'lsinfo', + 'move "0:1" "2"', + 'move "0" "1"', + 'moveid "%(id)s" "1"', + 'next', +# 'notcommands', +# 'outputs', +# 'password', + 'pause', +# 'ping', + 'play', + 'playid "%(id)s"', +# 'playlist', + 'playlistadd "foo" "%(url)s"', + 'playlistclear "foo"', + 'playlistadd "foo" "%(url)s"', + 'playlistdelete "foo" "0"', +# 'playlistfind', +# 'playlistid', +# 'playlistinfo', + 'playlistadd "foo" "%(url)s"', + 'playlistadd "foo" "%(url)s"', + 'playlistmove "foo" "0" "1"', +# 'playlistsearch', +# 'plchanges', +# 'plchangesposid', + 'previous', + 'random "1"', + 'random "0"', + 'rm "bar"', + 'rename "foo" "bar"', + 'repeat "0"', + 'rm "bar"', + 'save "bar"', + 'load "bar"', +# 'search', + 'seek "1" "10"', + 'seekid "%(id)s" "10"', +# 'setvol "10"', + 'shuffle', + 'shuffle "0:1"', + 'single "1"', + 'single "0"', +# 'stats', +# 'status', + 'stop', + 'swap "1" "2"', + 'swapid "%(id)s" "%(id2)s"', +# 'tagtypes', +# 'update', +# 'urlhandlers', +# 'volume', +] + + +def create_socketfile(): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect((host, port)) + sock.settimeout(0.5) + fd = sock.makefile('rw', 1) # 1 = line buffered + fd.readline() # Read banner + return fd + + +def wait(fd, prefix=None, collect=None): + while True: + line = fd.readline().rstrip() + if prefix: + logging.debug('%s: %s', prefix, repr(line)) + if line.split()[0] in ('OK', 'ACK'): + break + + +def collect_ids(fd): + fd.write('playlistinfo\n') + + ids = [] + while True: + line = fd.readline() + if line.split()[0] == 'OK': + break + if line.split()[0] == 'Id:': + ids.append(line.split()[1]) + return ids + + +def main(): + subsystems = {} + + command = create_socketfile() + + for test in test_requests: + # Remove any old ids + del data['id'] + del data['id2'] + + # Run setup code to force MPD into known state + for setup in setup_requests: + command.write(setup % data + '\n') + wait(command) + + data['id'], data['id2'] = collect_ids(command)[:2] + + # This connection needs to be make after setup commands are done or + # else they will cause idle events. + idle = create_socketfile() + + # Wait for new idle events + idle.write('idle\n') + + test = test % data + + logging.debug('idle: %s', repr('idle')) + logging.debug('command: %s', repr(test)) + + command.write(test + '\n') + wait(command, prefix='command') + + while True: + try: + line = idle.readline().rstrip() + except socket.timeout: + # Abort try if we time out. + idle.write('noidle\n') + break + + logging.debug('idle: %s', repr(line)) + + if line == 'OK': + break + + request_type = test.split()[0] + subsystem = line.split()[1] + subsystems.setdefault(request_type, set()).add(subsystem) + + logging.debug('---') + + pprint.pprint(subsystems) + + +if __name__ == '__main__': + main() From e919dcf627de3f3eb8951cafc410ec2d347f2dd1 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 19 Jul 2011 00:49:43 +0200 Subject: [PATCH 02/43] Create helper for sending events to BackendListeners --- mopidy/backends/base/playback.py | 20 +++++--------------- mopidy/frontends/mpd/__init__.py | 2 +- mopidy/listeners.py | 12 ++++++++++++ 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/mopidy/backends/base/playback.py b/mopidy/backends/base/playback.py index 088a5ad4..78e5057d 100644 --- a/mopidy/backends/base/playback.py +++ b/mopidy/backends/base/playback.py @@ -464,27 +464,17 @@ class PlaybackController(object): logger.debug(u'Triggering started playing event') if self.current_track is None: return - ActorRegistry.broadcast({ - 'command': 'pykka_call', - 'attr_path': ('started_playing',), - 'args': [], - 'kwargs': {'track': self.current_track}, - }, target_class=BackendListener) + BackendListener.send('started_playing', + track=self.current_track) def _trigger_stopped_playing_event(self): # TODO Test that this is called on next/prev/end-of-track logger.debug(u'Triggering stopped playing event') if self.current_track is None: return - ActorRegistry.broadcast({ - 'command': 'pykka_call', - 'attr_path': ('stopped_playing',), - 'args': [], - 'kwargs': { - 'track': self.current_track, - 'time_position': self.time_position, - }, - }, target_class=BackendListener) + BackendListener.send('stopped_playing', + track=self.current_track, + time_position=self.time_position) class BasePlaybackProvider(object): diff --git a/mopidy/frontends/mpd/__init__.py b/mopidy/frontends/mpd/__init__.py index 4deb7b89..561f9295 100644 --- a/mopidy/frontends/mpd/__init__.py +++ b/mopidy/frontends/mpd/__init__.py @@ -67,7 +67,7 @@ class MpdSession(network.LineProtocol): logger.debug(u'Response to [%s]:%s from %s: %s', self.host, self.port, self.actor_urn, log.indent(self.terminator.join(response))) - + self.send_lines(response) def close(self): diff --git a/mopidy/listeners.py b/mopidy/listeners.py index dfc5c60b..9977d5da 100644 --- a/mopidy/listeners.py +++ b/mopidy/listeners.py @@ -1,3 +1,5 @@ +from pykka import registry + class BackendListener(object): """ Marker interface for recipients of events sent by the backend. @@ -9,6 +11,16 @@ class BackendListener(object): interested in all events. """ + @staticmethod + def send(event, **kwargs): + """Helper to allow calling of backend listener events""" + registry.ActorRegistry.broadcast({ + 'command': 'pykka_call', + 'attr_path': (event,), + 'args': [], + 'kwargs': kwargs + }, target_class=BackendListener) + def started_playing(self, track): """ Called whenever a new track starts playing. From 215ed61b0b9b52078b15a93d35d374012ce355a0 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 19 Jul 2011 02:29:40 +0200 Subject: [PATCH 03/43] Update existing listener events to reflect that they only notify about track changes --- mopidy/backends/base/playback.py | 23 +++++++++++------------ mopidy/frontends/lastfm.py | 4 ++-- mopidy/listeners.py | 6 +++--- tests/backends/events_test.py | 24 ++++++++++++------------ tests/listeners_test.py | 8 ++++---- 5 files changed, 32 insertions(+), 33 deletions(-) diff --git a/mopidy/backends/base/playback.py b/mopidy/backends/base/playback.py index 78e5057d..7b697781 100644 --- a/mopidy/backends/base/playback.py +++ b/mopidy/backends/base/playback.py @@ -326,7 +326,7 @@ class PlaybackController(object): original_cp_track = self.current_cp_track if self.cp_track_at_eot: - self._trigger_stopped_playing_event() + self._trigger_track_playback_ended() self.play(self.cp_track_at_eot) else: self.stop(clear_current_track=True) @@ -354,7 +354,7 @@ class PlaybackController(object): return if self.cp_track_at_next: - self._trigger_stopped_playing_event() + self._trigger_track_playback_ended() self.play(self.cp_track_at_next) else: self.stop(clear_current_track=True) @@ -402,7 +402,7 @@ class PlaybackController(object): if self.random and self.current_cp_track in self._shuffled: self._shuffled.remove(self.current_cp_track) - self._trigger_started_playing_event() + self._trigger_track_playback_started() def previous(self): """Play the previous track.""" @@ -410,7 +410,7 @@ class PlaybackController(object): return if self.state == self.STOPPED: return - self._trigger_stopped_playing_event() + self._trigger_track_playback_ended() self.play(self.cp_track_at_previous, on_error_step=-1) def resume(self): @@ -454,25 +454,24 @@ class PlaybackController(object): :type clear_current_track: boolean """ if self.state != self.STOPPED: - self._trigger_stopped_playing_event() if self.provider.stop(): + self._trigger_track_playback_ended() self.state = self.STOPPED if clear_current_track: self.current_cp_track = None - def _trigger_started_playing_event(self): - logger.debug(u'Triggering started playing event') + def _trigger_track_playback_started(self): + logger.debug(u'Triggering track playback started event') if self.current_track is None: return - BackendListener.send('started_playing', + BackendListener.send('track_playback_started', track=self.current_track) - def _trigger_stopped_playing_event(self): - # TODO Test that this is called on next/prev/end-of-track - logger.debug(u'Triggering stopped playing event') + def _trigger_track_playback_ended(self): + logger.debug(u'Triggering track playback ended event') if self.current_track is None: return - BackendListener.send('stopped_playing', + BackendListener.send('track_playback_ended', track=self.current_track, time_position=self.time_position) diff --git a/mopidy/frontends/lastfm.py b/mopidy/frontends/lastfm.py index d50f8dd8..125457cd 100644 --- a/mopidy/frontends/lastfm.py +++ b/mopidy/frontends/lastfm.py @@ -57,7 +57,7 @@ class LastfmFrontend(ThreadingActor, BackendListener): logger.error(u'Error during Last.fm setup: %s', e) self.stop() - def started_playing(self, track): + def track_playback_started(self, track): artists = ', '.join([a.name for a in track.artists]) duration = track.length and track.length // 1000 or 0 self.last_start_time = int(time.time()) @@ -74,7 +74,7 @@ class LastfmFrontend(ThreadingActor, BackendListener): pylast.MalformedResponseError, pylast.WSError) as e: logger.warning(u'Error submitting playing track to Last.fm: %s', e) - def stopped_playing(self, track, time_position): + def track_playback_ended(self, track, time_position): artists = ', '.join([a.name for a in track.artists]) duration = track.length and track.length // 1000 or 0 time_position = time_position // 1000 diff --git a/mopidy/listeners.py b/mopidy/listeners.py index 9977d5da..bcce0c40 100644 --- a/mopidy/listeners.py +++ b/mopidy/listeners.py @@ -21,7 +21,7 @@ class BackendListener(object): 'kwargs': kwargs }, target_class=BackendListener) - def started_playing(self, track): + def track_playback_started(self, track): """ Called whenever a new track starts playing. @@ -32,9 +32,9 @@ class BackendListener(object): """ pass - def stopped_playing(self, track, time_position): + def track_playback_ended(self, track, time_position): """ - Called whenever playback is stopped. + Called whenever playback of a track ends. *MAY* be implemented by actor. diff --git a/tests/backends/events_test.py b/tests/backends/events_test.py index 44529e90..bc39ac00 100644 --- a/tests/backends/events_test.py +++ b/tests/backends/events_test.py @@ -11,8 +11,8 @@ from mopidy.models import Track class BackendEventsTest(unittest.TestCase): def setUp(self): self.events = { - 'started_playing': threading.Event(), - 'stopped_playing': threading.Event(), + 'track_playback_started': threading.Event(), + 'track_playback_ended': threading.Event(), } self.backend = DummyBackend.start().proxy() self.listener = DummyBackendListener.start(self.events).proxy() @@ -20,26 +20,26 @@ class BackendEventsTest(unittest.TestCase): def tearDown(self): ActorRegistry.stop_all() - def test_play_sends_started_playing_event(self): + def test_play_sends_track_playback_started_event(self): self.backend.current_playlist.add([Track(uri='a')]) self.backend.playback.play() - self.events['started_playing'].wait(timeout=1) - self.assertTrue(self.events['started_playing'].is_set()) + self.events['track_playback_started'].wait(timeout=1) + self.assertTrue(self.events['track_playback_started'].is_set()) - def test_stop_sends_stopped_playing_event(self): + def test_stop_sends_track_playback_ended_event(self): self.backend.current_playlist.add([Track(uri='a')]) self.backend.playback.play() self.backend.playback.stop() - self.events['stopped_playing'].wait(timeout=1) - self.assertTrue(self.events['stopped_playing'].is_set()) + self.events['track_playback_ended'].wait(timeout=1) + self.assertTrue(self.events['track_playback_ended'].is_set()) class DummyBackendListener(ThreadingActor, BackendListener): def __init__(self, events): self.events = events - def started_playing(self, track): - self.events['started_playing'].set() + def track_playback_started(self, track): + self.events['track_playback_started'].set() - def stopped_playing(self, track, time_position): - self.events['stopped_playing'].set() + def track_playback_ended(self, track, time_position): + self.events['track_playback_ended'].set() diff --git a/tests/listeners_test.py b/tests/listeners_test.py index 761aff4f..2c31efdb 100644 --- a/tests/listeners_test.py +++ b/tests/listeners_test.py @@ -7,8 +7,8 @@ class BackendListenerTest(unittest.TestCase): def setUp(self): self.listener = BackendListener() - def test_listener_has_default_impl_for_the_started_playing_event(self): - self.listener.started_playing(Track()) + def test_listener_has_default_impl_for_the_track_playback_started(self): + self.listener.track_playback_started(Track()) - def test_listener_has_default_impl_for_the_stopped_playing_event(self): - self.listener.stopped_playing(Track(), 0) + def test_listener_has_default_impl_for_the_track_playback_ended(self): + self.listener.track_playback_ended(Track(), 0) From 1e5a5fb7d0843c79d32fdc0029728dbd2f25f5bf Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 19 Jul 2011 02:43:48 +0200 Subject: [PATCH 04/43] Add playback state changed event --- mopidy/backends/base/playback.py | 8 +++++++- mopidy/listeners.py | 8 ++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/mopidy/backends/base/playback.py b/mopidy/backends/base/playback.py index 7b697781..1b5e1c9f 100644 --- a/mopidy/backends/base/playback.py +++ b/mopidy/backends/base/playback.py @@ -276,6 +276,9 @@ class PlaybackController(object): def state(self, new_state): (old_state, self._state) = (self.state, new_state) logger.debug(u'Changing state: %s -> %s', old_state, new_state) + + self._trigger_playback_state_changed() + # FIXME play_time stuff assumes backend does not have a better way of # handeling this stuff :/ if (old_state in (self.PLAYING, self.STOPPED) @@ -387,7 +390,6 @@ class PlaybackController(object): self.resume() if cp_track is not None: - self.state = self.STOPPED self.current_cp_track = cp_track self.state = self.PLAYING if not self.provider.play(cp_track.track): @@ -475,6 +477,10 @@ class PlaybackController(object): track=self.current_track, time_position=self.time_position) + def _trigger_playback_state_changed(self): + logger.debug(u'Triggering playback state change event') + BackendListener.send('playback_state_changed') + class BasePlaybackProvider(object): """ diff --git a/mopidy/listeners.py b/mopidy/listeners.py index bcce0c40..f6973eaf 100644 --- a/mopidy/listeners.py +++ b/mopidy/listeners.py @@ -44,3 +44,11 @@ class BackendListener(object): :type time_position: int """ pass + + def playback_state_changed(self): + """ + Called whenever playback state is changed. + + *MAY* be implemented by actor. + """ + pass From ce1a0118d80500a31b55de2874e258deb69c5dd1 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 19 Jul 2011 02:54:27 +0200 Subject: [PATCH 05/43] Add event that indicates playlist whenever version is changed --- mopidy/backends/base/current_playlist.py | 20 ++++++++++++++++---- mopidy/listeners.py | 8 ++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/mopidy/backends/base/current_playlist.py b/mopidy/backends/base/current_playlist.py index 2633f166..e89c23d5 100644 --- a/mopidy/backends/base/current_playlist.py +++ b/mopidy/backends/base/current_playlist.py @@ -2,6 +2,7 @@ from copy import copy import logging import random +from mopidy.listeners import BackendListener from mopidy.models import CpTrack logger = logging.getLogger('mopidy.backends.base') @@ -16,6 +17,7 @@ class CurrentPlaylistController(object): def __init__(self, backend): self.backend = backend + self.cp_id = 0 self._cp_tracks = [] self._version = 0 @@ -53,8 +55,9 @@ class CurrentPlaylistController(object): def version(self, version): self._version = version self.backend.playback.on_current_playlist_change() + self._trigger_playlist_changed() - def add(self, track, at_position=None): + def add(self, track, at_position=None, increase_version=True): """ Add the track to the end of, or at the given position in the current playlist. @@ -68,12 +71,14 @@ class CurrentPlaylistController(object): """ assert at_position <= len(self._cp_tracks), \ u'at_position can not be greater than playlist length' - cp_track = CpTrack(self.version, track) + cp_track = CpTrack(self.cp_id, track) if at_position is not None: self._cp_tracks.insert(at_position, cp_track) else: self._cp_tracks.append(cp_track) - self.version += 1 + if increase_version: + self.version += 1 + self.cp_id += 1 return cp_track def append(self, tracks): @@ -84,7 +89,10 @@ class CurrentPlaylistController(object): :type tracks: list of :class:`mopidy.models.Track` """ for track in tracks: - self.add(track) + self.add(track, increase_version=False) + + if tracks: + self.version += 1 def clear(self): """Clear the current playlist.""" @@ -199,3 +207,7 @@ class CurrentPlaylistController(object): random.shuffle(shuffled) self._cp_tracks = before + shuffled + after self.version += 1 + + def _trigger_playlist_changed(self): + logger.debug(u'Triggering playlist changed event') + BackendListener.send('playlist_changed') diff --git a/mopidy/listeners.py b/mopidy/listeners.py index f6973eaf..397e08ea 100644 --- a/mopidy/listeners.py +++ b/mopidy/listeners.py @@ -52,3 +52,11 @@ class BackendListener(object): *MAY* be implemented by actor. """ pass + + def playlist_changed(self): + """ + Called whenever a playlist is changed. + + *MAY* be implemented by actor. + """ + pass From 4f124480c3998b1d0911f72fd44e535c62b03ffe Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 19 Jul 2011 03:08:29 +0200 Subject: [PATCH 06/43] Add an options wrapper to magically pick up changes --- mopidy/backends/base/playback.py | 23 +++++++++++++++++++---- mopidy/listeners.py | 8 ++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/mopidy/backends/base/playback.py b/mopidy/backends/base/playback.py index 1b5e1c9f..5cab8229 100644 --- a/mopidy/backends/base/playback.py +++ b/mopidy/backends/base/playback.py @@ -8,6 +8,17 @@ from mopidy.listeners import BackendListener logger = logging.getLogger('mopidy.backends.base') + +def option_wrapper(name, default): + def get_option(self): + return getattr(self, name, default) + def set_option(self, value): + if getattr(self, name, default) != value: + self._trigger_options_changed() + return setattr(self, name, value) + return property(get_option, set_option) + + class PlaybackController(object): """ :param backend: the backend @@ -34,7 +45,7 @@ class PlaybackController(object): #: Tracks are removed from the playlist when they have been played. #: :class:`False` #: Tracks are not removed from the playlist. - consume = False + consume = option_wrapper('_consume', False) #: The currently playing or selected track. #: @@ -46,21 +57,21 @@ class PlaybackController(object): #: Tracks are selected at random from the playlist. #: :class:`False` #: Tracks are played in the order of the playlist. - random = False + random = option_wrapper('_random', False) #: :class:`True` #: The current playlist is played repeatedly. To repeat a single track, #: select both :attr:`repeat` and :attr:`single`. #: :class:`False` #: The current playlist is played once. - repeat = False + repeat = option_wrapper('_repeat', False) #: :class:`True` #: Playback is stopped after current song, unless in :attr:`repeat` #: mode. #: :class:`False` #: Playback continues after current song. - single = False + single = option_wrapper('_single', False) def __init__(self, backend, provider): self.backend = backend @@ -481,6 +492,10 @@ class PlaybackController(object): logger.debug(u'Triggering playback state change event') BackendListener.send('playback_state_changed') + def _trigger_options_changed(self): + logger.debug(u'Triggering options changed event') + BackendListener.send('options_changed') + class BasePlaybackProvider(object): """ diff --git a/mopidy/listeners.py b/mopidy/listeners.py index 397e08ea..c0453a2b 100644 --- a/mopidy/listeners.py +++ b/mopidy/listeners.py @@ -60,3 +60,11 @@ class BackendListener(object): *MAY* be implemented by actor. """ pass + + def options_changed(self): + """ + Called whenever an option is changed. + + *MAY* be implemented by actor. + """ + pass From 8ae0381cd8d54b81e31fea994397d62347a44a18 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 19 Jul 2011 03:44:34 +0200 Subject: [PATCH 07/43] Start adding idle to frontend, mpd-session and dispatcher --- mopidy/frontends/mpd/__init__.py | 28 +++++++++++++++++++++++++--- mopidy/frontends/mpd/dispatcher.py | 3 +++ mopidy/listeners.py | 4 +++- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/mopidy/frontends/mpd/__init__.py b/mopidy/frontends/mpd/__init__.py index 561f9295..6f6e3bfc 100644 --- a/mopidy/frontends/mpd/__init__.py +++ b/mopidy/frontends/mpd/__init__.py @@ -1,15 +1,15 @@ import logging import sys -from pykka.actor import ThreadingActor +from pykka import registry, actor -from mopidy import settings +from mopidy import listeners, settings from mopidy.frontends.mpd import dispatcher, protocol from mopidy.utils import network, process, log logger = logging.getLogger('mopidy.frontends.mpd') -class MpdFrontend(ThreadingActor): +class MpdFrontend(actor.ThreadingActor, listeners.BackendListener): """ The MPD frontend. @@ -39,6 +39,25 @@ class MpdFrontend(ThreadingActor): def on_stop(self): process.stop_actors_by_class(MpdSession) + def send_idle(self, subsystem): + # FIXME this should be updated once pykka supports non-blocking calls + # on proxies or some similar solution + registry.ActorRegistry.broadcast({ + 'command': 'pykka_call', + 'attr_path': ('on_idle',), + 'args': [subsystem], + 'kwargs': {}, + }, target_class=MpdSession) + + def playback_state_changed(self): + self.send_idle('player') + + def playlist_changed(self): + self.send_idle('playlist') + + def options_changed(self): + self.send_idle('options') + class MpdSession(network.LineProtocol): """ @@ -70,5 +89,8 @@ class MpdSession(network.LineProtocol): self.send_lines(response) + def on_idle(self, subsystem): + self.dispatcher.handle_idle(subsystem) + def close(self): self.stop() diff --git a/mopidy/frontends/mpd/dispatcher.py b/mopidy/frontends/mpd/dispatcher.py index 0f0f0299..6cc05bec 100644 --- a/mopidy/frontends/mpd/dispatcher.py +++ b/mopidy/frontends/mpd/dispatcher.py @@ -47,6 +47,9 @@ class MpdDispatcher(object): ] return self._call_next_filter(request, response, filter_chain) + def handle_idle(self, subsystem): + logger.debug(u'Got idle event for %s', subsystem) + def _call_next_filter(self, request, response, filter_chain): if filter_chain: next_filter = filter_chain.pop(0) diff --git a/mopidy/listeners.py b/mopidy/listeners.py index c0453a2b..5fbccff5 100644 --- a/mopidy/listeners.py +++ b/mopidy/listeners.py @@ -14,11 +14,13 @@ class BackendListener(object): @staticmethod def send(event, **kwargs): """Helper to allow calling of backend listener events""" + # FIXME this should be updated once pykka supports non-blocking calls + # on proxies or some similar solution registry.ActorRegistry.broadcast({ 'command': 'pykka_call', 'attr_path': (event,), 'args': [], - 'kwargs': kwargs + 'kwargs': kwargs, }, target_class=BackendListener) def track_playback_started(self, track): From da3b4c4b93fbd51b58a8ccd7974306253d1ec8d8 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 19 Jul 2011 11:07:36 +0200 Subject: [PATCH 08/43] Add changed event for volume and updated MpdSession to regcognise it. --- mopidy/frontends/mpd/__init__.py | 3 +++ mopidy/listeners.py | 8 ++++++++ mopidy/mixers/base.py | 11 ++++++++++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/mopidy/frontends/mpd/__init__.py b/mopidy/frontends/mpd/__init__.py index 6f6e3bfc..ff239458 100644 --- a/mopidy/frontends/mpd/__init__.py +++ b/mopidy/frontends/mpd/__init__.py @@ -58,6 +58,9 @@ class MpdFrontend(actor.ThreadingActor, listeners.BackendListener): def options_changed(self): self.send_idle('options') + def volume_changed(self): + self.send_idle('mixer') + class MpdSession(network.LineProtocol): """ diff --git a/mopidy/listeners.py b/mopidy/listeners.py index 5fbccff5..590f0ad0 100644 --- a/mopidy/listeners.py +++ b/mopidy/listeners.py @@ -70,3 +70,11 @@ class BackendListener(object): *MAY* be implemented by actor. """ pass + + def volume_changed(self): + """ + Called whenever the volume is changed. + + *MAY* be implemented by actor. + """ + pass diff --git a/mopidy/mixers/base.py b/mopidy/mixers/base.py index ec3d8ae5..8798076a 100644 --- a/mopidy/mixers/base.py +++ b/mopidy/mixers/base.py @@ -1,4 +1,8 @@ -from mopidy import settings +import logging + +from mopidy import listeners, settings + +logger = logging.getLogger('mopdy.mixers') class BaseMixer(object): """ @@ -30,6 +34,7 @@ class BaseMixer(object): elif volume > 100: volume = 100 self.set_volume(volume) + self._trigger_volume_changed() def get_volume(self): """ @@ -46,3 +51,7 @@ class BaseMixer(object): *MUST be implemented by subclass.* """ raise NotImplementedError + + def _trigger_volume_changed(self): + logger.debug(u'Triggering volume changed event') + listeners.BackendListener.send('volume_changed') From e050c1325154992a9776db9f18d40c42338809c9 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 19 Jul 2011 21:08:14 +0200 Subject: [PATCH 09/43] Remove most of pykka logging which we don't need unless debuging --- mopidy/core.py | 2 +- mopidy/utils/log.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/mopidy/core.py b/mopidy/core.py index e831fc55..cec8aef9 100644 --- a/mopidy/core.py +++ b/mopidy/core.py @@ -69,7 +69,7 @@ def parse_options(): action='store_const', const=0, dest='verbosity_level', help='less output (warning level)') parser.add_option('-v', '--verbose', - action='store_const', const=2, dest='verbosity_level', + action='count', default=1, dest='verbosity_level', help='more output (debug level)') parser.add_option('--save-debug-log', action='store_true', dest='save_debug_log', diff --git a/mopidy/utils/log.py b/mopidy/utils/log.py index 03b85b48..0e5dfc29 100644 --- a/mopidy/utils/log.py +++ b/mopidy/utils/log.py @@ -20,7 +20,7 @@ def setup_console_logging(verbosity_level): if verbosity_level == 0: log_level = logging.WARNING log_format = settings.CONSOLE_LOG_FORMAT - elif verbosity_level == 2: + elif verbosity_level >= 2: log_level = logging.DEBUG log_format = settings.DEBUG_LOG_FORMAT else: @@ -33,6 +33,9 @@ def setup_console_logging(verbosity_level): root = logging.getLogger('') root.addHandler(handler) + if verbosity_level < 3: + logging.getLogger('pykka').setLevel(logging.INFO) + def setup_debug_logging_to_file(): formatter = logging.Formatter(settings.DEBUG_LOG_FORMAT) handler = logging.handlers.RotatingFileHandler( From da9b6470ba739d6197f732966d4311ba9ed3f225 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Tue, 19 Jul 2011 22:41:58 +0200 Subject: [PATCH 10/43] Switched playback test over to testing via MpdSession slightly higher level testing --- mopidy/frontends/mpd/__init__.py | 6 +- tests/frontends/mpd/protocol/__init__.py | 43 +++ .../mpd/{ => protocol}/playback_test.py | 311 +++++++++--------- 3 files changed, 203 insertions(+), 157 deletions(-) create mode 100644 tests/frontends/mpd/protocol/__init__.py rename tests/frontends/mpd/{ => protocol}/playback_test.py (55%) diff --git a/mopidy/frontends/mpd/__init__.py b/mopidy/frontends/mpd/__init__.py index 4deb7b89..8b6d3770 100644 --- a/mopidy/frontends/mpd/__init__.py +++ b/mopidy/frontends/mpd/__init__.py @@ -49,8 +49,8 @@ class MpdSession(network.LineProtocol): terminator = protocol.LINE_TERMINATOR encoding = protocol.ENCODING - def __init__(self, client): - super(MpdSession, self).__init__(client) + def __init__(self, connection): + super(MpdSession, self).__init__(connection) self.dispatcher = dispatcher.MpdDispatcher(self) def on_start(self): @@ -67,7 +67,7 @@ class MpdSession(network.LineProtocol): logger.debug(u'Response to [%s]:%s from %s: %s', self.host, self.port, self.actor_urn, log.indent(self.terminator.join(response))) - + self.send_lines(response) def close(self): diff --git a/tests/frontends/mpd/protocol/__init__.py b/tests/frontends/mpd/protocol/__init__.py new file mode 100644 index 00000000..a3705c48 --- /dev/null +++ b/tests/frontends/mpd/protocol/__init__.py @@ -0,0 +1,43 @@ +import unittest +import mock + +from mopidy.backends import dummy as backend +from mopidy.frontends import mpd +from mopidy.frontends.mpd import dispatcher +from mopidy.mixers import dummy as mixer +from mopidy.utils import network + + +class MockConnetion(mock.Mock): + def __init__(self, *args, **kwargs): + super(MockConnetion, self).__init__(*args, **kwargs) + self.host = mock.sentinel.host + self.port = mock.sentinel.port + self.response = [] + + def send(self, data): + self.response.extend(data.split('\n')) + + +class BaseTestCase(unittest.TestCase): + def setUp(self): + self.backend = backend.DummyBackend.start().proxy() + self.mixer = mixer.DummyMixer.start().proxy() + self.dispatcher = dispatcher.MpdDispatcher() + + self.connection = MockConnetion() + self.session = mpd.MpdSession(self.connection) + + def tearDown(self): + self.backend.stop().get() + self.mixer.stop().get() + + def sendRequest(self, request, clear=False): + self.connection.response = [] + self.session.on_line_received(request) + + def assertResponse(self, value, index=None): + if index is not None: + self.assertEqual(value, self.connection.response[index]) + else: + self.assert_(value in self.connection.response) diff --git a/tests/frontends/mpd/playback_test.py b/tests/frontends/mpd/protocol/playback_test.py similarity index 55% rename from tests/frontends/mpd/playback_test.py rename to tests/frontends/mpd/protocol/playback_test.py index e80943d6..b9129688 100644 --- a/tests/frontends/mpd/playback_test.py +++ b/tests/frontends/mpd/protocol/playback_test.py @@ -1,163 +1,150 @@ -import unittest - from mopidy.backends.base import PlaybackController -from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpd.dispatcher import MpdDispatcher -from mopidy.mixers.dummy import DummyMixer from mopidy.models import Track from tests import SkipTest +from tests.frontends.mpd import protocol PAUSED = PlaybackController.PAUSED PLAYING = PlaybackController.PLAYING STOPPED = PlaybackController.STOPPED -class PlaybackOptionsHandlerTest(unittest.TestCase): - def setUp(self): - self.backend = DummyBackend.start().proxy() - self.mixer = DummyMixer.start().proxy() - self.dispatcher = MpdDispatcher() - - def tearDown(self): - self.backend.stop().get() - self.mixer.stop().get() +class PlaybackOptionsHandlerTest(protocol.BaseTestCase): def test_consume_off(self): - result = self.dispatcher.handle_request(u'consume "0"') + self.sendRequest(u'consume "0"') self.assertFalse(self.backend.playback.consume.get()) - self.assert_(u'OK' in result) + self.assertResponse(u'OK') def test_consume_off_without_quotes(self): - result = self.dispatcher.handle_request(u'consume 0') + self.sendRequest(u'consume 0') self.assertFalse(self.backend.playback.consume.get()) - self.assert_(u'OK' in result) + self.assertResponse(u'OK') def test_consume_on(self): - result = self.dispatcher.handle_request(u'consume "1"') + self.sendRequest(u'consume "1"') self.assertTrue(self.backend.playback.consume.get()) - self.assert_(u'OK' in result) + self.assertResponse(u'OK') def test_consume_on_without_quotes(self): - result = self.dispatcher.handle_request(u'consume 1') + self.sendRequest(u'consume 1') self.assertTrue(self.backend.playback.consume.get()) - self.assert_(u'OK' in result) + self.assertResponse(u'OK') def test_crossfade(self): - result = self.dispatcher.handle_request(u'crossfade "10"') - self.assert_(u'ACK [0@0] {} Not implemented' in result) + self.sendRequest(u'crossfade "10"') + self.assertResponse(u'ACK [0@0] {} Not implemented') def test_random_off(self): - result = self.dispatcher.handle_request(u'random "0"') + self.sendRequest(u'random "0"') self.assertFalse(self.backend.playback.random.get()) - self.assert_(u'OK' in result) + self.assertResponse(u'OK') def test_random_off_without_quotes(self): - result = self.dispatcher.handle_request(u'random 0') + self.sendRequest(u'random 0') self.assertFalse(self.backend.playback.random.get()) - self.assert_(u'OK' in result) + self.assertResponse(u'OK') def test_random_on(self): - result = self.dispatcher.handle_request(u'random "1"') + self.sendRequest(u'random "1"') self.assertTrue(self.backend.playback.random.get()) - self.assert_(u'OK' in result) + self.assertResponse(u'OK') def test_random_on_without_quotes(self): - result = self.dispatcher.handle_request(u'random 1') + self.sendRequest(u'random 1') self.assertTrue(self.backend.playback.random.get()) - self.assert_(u'OK' in result) + self.assertResponse(u'OK') def test_repeat_off(self): - result = self.dispatcher.handle_request(u'repeat "0"') + self.sendRequest(u'repeat "0"') self.assertFalse(self.backend.playback.repeat.get()) - self.assert_(u'OK' in result) + self.assertResponse(u'OK') def test_repeat_off_without_quotes(self): - result = self.dispatcher.handle_request(u'repeat 0') + self.sendRequest(u'repeat 0') self.assertFalse(self.backend.playback.repeat.get()) - self.assert_(u'OK' in result) + self.assertResponse(u'OK') def test_repeat_on(self): - result = self.dispatcher.handle_request(u'repeat "1"') + self.sendRequest(u'repeat "1"') self.assertTrue(self.backend.playback.repeat.get()) - self.assert_(u'OK' in result) + self.assertResponse(u'OK') def test_repeat_on_without_quotes(self): - result = self.dispatcher.handle_request(u'repeat 1') + self.sendRequest(u'repeat 1') self.assertTrue(self.backend.playback.repeat.get()) - self.assert_(u'OK' in result) + self.assertResponse(u'OK') def test_setvol_below_min(self): - result = self.dispatcher.handle_request(u'setvol "-10"') - self.assert_(u'OK' in result) + self.sendRequest(u'setvol "-10"') self.assertEqual(0, self.mixer.volume.get()) + self.assertResponse(u'OK') def test_setvol_min(self): - result = self.dispatcher.handle_request(u'setvol "0"') - self.assert_(u'OK' in result) + self.sendRequest(u'setvol "0"') self.assertEqual(0, self.mixer.volume.get()) + self.assertResponse(u'OK') def test_setvol_middle(self): - result = self.dispatcher.handle_request(u'setvol "50"') - self.assert_(u'OK' in result) + self.sendRequest(u'setvol "50"') self.assertEqual(50, self.mixer.volume.get()) + self.assertResponse(u'OK') def test_setvol_max(self): - result = self.dispatcher.handle_request(u'setvol "100"') - self.assert_(u'OK' in result) + self.sendRequest(u'setvol "100"') self.assertEqual(100, self.mixer.volume.get()) + self.assertResponse(u'OK') def test_setvol_above_max(self): - result = self.dispatcher.handle_request(u'setvol "110"') - self.assert_(u'OK' in result) + self.sendRequest(u'setvol "110"') self.assertEqual(100, self.mixer.volume.get()) + self.assertResponse(u'OK') def test_setvol_plus_is_ignored(self): - result = self.dispatcher.handle_request(u'setvol "+10"') - self.assert_(u'OK' in result) + self.sendRequest(u'setvol "+10"') self.assertEqual(10, self.mixer.volume.get()) + self.assertResponse(u'OK') def test_setvol_without_quotes(self): - result = self.dispatcher.handle_request(u'setvol 50') - self.assert_(u'OK' in result) + self.sendRequest(u'setvol 50') self.assertEqual(50, self.mixer.volume.get()) + self.assertResponse(u'OK') def test_single_off(self): - result = self.dispatcher.handle_request(u'single "0"') + self.sendRequest(u'single "0"') self.assertFalse(self.backend.playback.single.get()) - self.assert_(u'OK' in result) + self.assertResponse(u'OK') def test_single_off_without_quotes(self): - result = self.dispatcher.handle_request(u'single 0') + self.sendRequest(u'single 0') self.assertFalse(self.backend.playback.single.get()) - self.assert_(u'OK' in result) + self.assertResponse(u'OK') def test_single_on(self): - result = self.dispatcher.handle_request(u'single "1"') + self.sendRequest(u'single "1"') self.assertTrue(self.backend.playback.single.get()) - self.assert_(u'OK' in result) + self.assertResponse(u'OK') def test_single_on_without_quotes(self): - result = self.dispatcher.handle_request(u'single 1') + self.sendRequest(u'single 1') self.assertTrue(self.backend.playback.single.get()) - self.assert_(u'OK' in result) + self.assertResponse(u'OK') def test_replay_gain_mode_off(self): - result = self.dispatcher.handle_request(u'replay_gain_mode "off"') - self.assert_(u'ACK [0@0] {} Not implemented' in result) + self.sendRequest(u'replay_gain_mode "off"') + self.assertResponse(u'ACK [0@0] {} Not implemented') def test_replay_gain_mode_track(self): - result = self.dispatcher.handle_request(u'replay_gain_mode "track"') - self.assert_(u'ACK [0@0] {} Not implemented' in result) + self.sendRequest(u'replay_gain_mode "track"') + self.assertResponse(u'ACK [0@0] {} Not implemented') def test_replay_gain_mode_album(self): - result = self.dispatcher.handle_request(u'replay_gain_mode "album"') - self.assert_(u'ACK [0@0] {} Not implemented' in result) + self.sendRequest(u'replay_gain_mode "album"') + self.assertResponse(u'ACK [0@0] {} Not implemented') def test_replay_gain_status_default(self): - expected = u'off' - result = self.dispatcher.handle_request(u'replay_gain_status') - self.assert_(u'OK' in result) - self.assert_(expected in result) + self.sendRequest(u'replay_gain_status') + self.assertResponse(u'OK') + self.assertResponse(u'off') def test_replay_gain_status_off(self): raise SkipTest # TODO @@ -169,79 +156,80 @@ class PlaybackOptionsHandlerTest(unittest.TestCase): raise SkipTest # TODO -class PlaybackControlHandlerTest(unittest.TestCase): - def setUp(self): - self.backend = DummyBackend.start().proxy() - self.mixer = DummyMixer.start().proxy() - self.dispatcher = MpdDispatcher() - - def tearDown(self): - self.backend.stop().get() - self.mixer.stop().get() - +class PlaybackControlHandlerTest(protocol.BaseTestCase): def test_next(self): - result = self.dispatcher.handle_request(u'next') - self.assert_(u'OK' in result) + self.sendRequest(u'next') + self.assertResponse(u'OK') def test_pause_off(self): self.backend.current_playlist.append([Track()]) - self.dispatcher.handle_request(u'play "0"') - self.dispatcher.handle_request(u'pause "1"') - result = self.dispatcher.handle_request(u'pause "0"') - self.assert_(u'OK' in result) + + self.sendRequest(u'play "0"') + self.sendRequest(u'pause "1"') + self.sendRequest(u'pause "0"') self.assertEqual(PLAYING, self.backend.playback.state.get()) + self.assertResponse(u'OK') def test_pause_on(self): self.backend.current_playlist.append([Track()]) - self.dispatcher.handle_request(u'play "0"') - result = self.dispatcher.handle_request(u'pause "1"') - self.assert_(u'OK' in result) + + self.sendRequest(u'play "0"') + self.sendRequest(u'pause "1"') self.assertEqual(PAUSED, self.backend.playback.state.get()) + self.assertResponse(u'OK') def test_pause_toggle(self): self.backend.current_playlist.append([Track()]) - result = self.dispatcher.handle_request(u'play "0"') - self.assert_(u'OK' in result) + + self.sendRequest(u'play "0"') self.assertEqual(PLAYING, self.backend.playback.state.get()) - result = self.dispatcher.handle_request(u'pause') - self.assert_(u'OK' in result) + self.assertResponse(u'OK') + + self.sendRequest(u'pause') self.assertEqual(PAUSED, self.backend.playback.state.get()) - result = self.dispatcher.handle_request(u'pause') - self.assert_(u'OK' in result) + self.assertResponse(u'OK') + + self.sendRequest(u'pause') self.assertEqual(PLAYING, self.backend.playback.state.get()) + self.assertResponse(u'OK') def test_play_without_pos(self): self.backend.current_playlist.append([Track()]) self.backend.playback.state = PAUSED - result = self.dispatcher.handle_request(u'play') - self.assert_(u'OK' in result) + + self.sendRequest(u'play') self.assertEqual(PLAYING, self.backend.playback.state.get()) + self.assertResponse(u'OK') def test_play_with_pos(self): self.backend.current_playlist.append([Track()]) - result = self.dispatcher.handle_request(u'play "0"') - self.assert_(u'OK' in result) + + self.sendRequest(u'play "0"') self.assertEqual(PLAYING, self.backend.playback.state.get()) + self.assertResponse(u'OK') def test_play_with_pos_without_quotes(self): self.backend.current_playlist.append([Track()]) - result = self.dispatcher.handle_request(u'play 0') - self.assert_(u'OK' in result) + + self.sendRequest(u'play 0') self.assertEqual(PLAYING, self.backend.playback.state.get()) + self.assertResponse(u'OK') def test_play_with_pos_out_of_bounds(self): self.backend.current_playlist.append([]) - result = self.dispatcher.handle_request(u'play "0"') - self.assertEqual(result[0], u'ACK [2@0] {play} Bad song index') + + self.sendRequest(u'play "0"') self.assertEqual(STOPPED, self.backend.playback.state.get()) + self.assertResponse(u'ACK [2@0] {play} Bad song index', index=0) def test_play_minus_one_plays_first_in_playlist_if_no_current_track(self): self.assertEqual(self.backend.playback.current_track.get(), None) self.backend.current_playlist.append([Track(uri='a'), Track(uri='b')]) - result = self.dispatcher.handle_request(u'play "-1"') - self.assert_(u'OK' in result) + + self.sendRequest(u'play "-1"') self.assertEqual(PLAYING, self.backend.playback.state.get()) - self.assertEqual(self.backend.playback.current_track.get().uri, 'a') + self.assertEqual('a', self.backend.playback.current_track.get().uri) + self.assertResponse(u'OK') def test_play_minus_one_plays_current_track_if_current_track_is_set(self): self.backend.current_playlist.append([Track(uri='a'), Track(uri='b')]) @@ -250,27 +238,30 @@ class PlaybackControlHandlerTest(unittest.TestCase): self.backend.playback.next() self.backend.playback.stop() self.assertNotEqual(self.backend.playback.current_track.get(), None) - result = self.dispatcher.handle_request(u'play "-1"') - self.assert_(u'OK' in result) + + self.sendRequest(u'play "-1"') self.assertEqual(PLAYING, self.backend.playback.state.get()) - self.assertEqual(self.backend.playback.current_track.get().uri, 'b') + self.assertEqual('b', self.backend.playback.current_track.get().uri) + self.assertResponse(u'OK') def test_play_minus_one_on_empty_playlist_does_not_ack(self): self.backend.current_playlist.clear() - result = self.dispatcher.handle_request(u'play "-1"') - self.assert_(u'OK' in result) + + self.sendRequest(u'play "-1"') self.assertEqual(STOPPED, self.backend.playback.state.get()) - self.assertEqual(self.backend.playback.current_track.get(), None) + self.assertEqual(None, self.backend.playback.current_track.get()) + self.assertResponse(u'OK') def test_play_minus_is_ignored_if_playing(self): self.backend.current_playlist.append([Track(length=40000)]) self.backend.playback.seek(30000) self.assert_(self.backend.playback.time_position.get() >= 30000) self.assertEquals(PLAYING, self.backend.playback.state.get()) - result = self.dispatcher.handle_request(u'play "-1"') - self.assert_(u'OK' in result) + + self.sendRequest(u'play "-1"') self.assertEqual(PLAYING, self.backend.playback.state.get()) self.assert_(self.backend.playback.time_position.get() >= 30000) + self.assertResponse(u'OK') def test_play_minus_one_resumes_if_paused(self): self.backend.current_playlist.append([Track(length=40000)]) @@ -279,24 +270,27 @@ class PlaybackControlHandlerTest(unittest.TestCase): self.assertEquals(PLAYING, self.backend.playback.state.get()) self.backend.playback.pause() self.assertEquals(PAUSED, self.backend.playback.state.get()) - result = self.dispatcher.handle_request(u'play "-1"') - self.assert_(u'OK' in result) + + self.sendRequest(u'play "-1"') self.assertEqual(PLAYING, self.backend.playback.state.get()) self.assert_(self.backend.playback.time_position.get() >= 30000) + self.assertResponse(u'OK') def test_playid(self): self.backend.current_playlist.append([Track()]) - result = self.dispatcher.handle_request(u'playid "0"') - self.assert_(u'OK' in result) + + self.sendRequest(u'playid "0"') self.assertEqual(PLAYING, self.backend.playback.state.get()) + self.assertResponse(u'OK') def test_playid_minus_one_plays_first_in_playlist_if_no_current_track(self): self.assertEqual(self.backend.playback.current_track.get(), None) self.backend.current_playlist.append([Track(uri='a'), Track(uri='b')]) - result = self.dispatcher.handle_request(u'playid "-1"') - self.assert_(u'OK' in result) + + self.sendRequest(u'playid "-1"') self.assertEqual(PLAYING, self.backend.playback.state.get()) - self.assertEqual(self.backend.playback.current_track.get().uri, 'a') + self.assertEqual('a', self.backend.playback.current_track.get().uri) + self.assertResponse(u'OK') def test_playid_minus_one_plays_current_track_if_current_track_is_set(self): self.backend.current_playlist.append([Track(uri='a'), Track(uri='b')]) @@ -304,28 +298,31 @@ class PlaybackControlHandlerTest(unittest.TestCase): self.backend.playback.play() self.backend.playback.next() self.backend.playback.stop() - self.assertNotEqual(self.backend.playback.current_track.get(), None) - result = self.dispatcher.handle_request(u'playid "-1"') - self.assert_(u'OK' in result) + self.assertNotEqual(None, self.backend.playback.current_track.get()) + + self.sendRequest(u'playid "-1"') self.assertEqual(PLAYING, self.backend.playback.state.get()) - self.assertEqual(self.backend.playback.current_track.get().uri, 'b') + self.assertEqual('b', self.backend.playback.current_track.get().uri) + self.assertResponse(u'OK') def test_playid_minus_one_on_empty_playlist_does_not_ack(self): self.backend.current_playlist.clear() - result = self.dispatcher.handle_request(u'playid "-1"') - self.assert_(u'OK' in result) + + self.sendRequest(u'playid "-1"') self.assertEqual(STOPPED, self.backend.playback.state.get()) - self.assertEqual(self.backend.playback.current_track.get(), None) + self.assertEqual(None, self.backend.playback.current_track.get()) + self.assertResponse(u'OK') def test_playid_minus_is_ignored_if_playing(self): self.backend.current_playlist.append([Track(length=40000)]) self.backend.playback.seek(30000) self.assert_(self.backend.playback.time_position.get() >= 30000) self.assertEquals(PLAYING, self.backend.playback.state.get()) - result = self.dispatcher.handle_request(u'playid "-1"') - self.assert_(u'OK' in result) + + self.sendRequest(u'playid "-1"') self.assertEqual(PLAYING, self.backend.playback.state.get()) self.assert_(self.backend.playback.time_position.get() >= 30000) + self.assertResponse(u'OK') def test_playid_minus_one_resumes_if_paused(self): self.backend.current_playlist.append([Track(length=40000)]) @@ -334,58 +331,64 @@ class PlaybackControlHandlerTest(unittest.TestCase): self.assertEquals(PLAYING, self.backend.playback.state.get()) self.backend.playback.pause() self.assertEquals(PAUSED, self.backend.playback.state.get()) - result = self.dispatcher.handle_request(u'playid "-1"') - self.assert_(u'OK' in result) + + self.sendRequest(u'playid "-1"') self.assertEqual(PLAYING, self.backend.playback.state.get()) self.assert_(self.backend.playback.time_position.get() >= 30000) + self.assertResponse(u'OK') def test_playid_which_does_not_exist(self): self.backend.current_playlist.append([Track()]) - result = self.dispatcher.handle_request(u'playid "12345"') - self.assertEqual(result[0], u'ACK [50@0] {playid} No such song') + + self.sendRequest(u'playid "12345"') + self.assertResponse(u'ACK [50@0] {playid} No such song', index=0) def test_previous(self): - result = self.dispatcher.handle_request(u'previous') - self.assert_(u'OK' in result) + self.sendRequest(u'previous') + self.assertResponse(u'OK') def test_seek(self): self.backend.current_playlist.append([Track(length=40000)]) - self.dispatcher.handle_request(u'seek "0"') - result = self.dispatcher.handle_request(u'seek "0" "30"') - self.assert_(u'OK' in result) + + self.sendRequest(u'seek "0"') + self.sendRequest(u'seek "0" "30"') self.assert_(self.backend.playback.time_position >= 30000) + self.assertResponse(u'OK') def test_seek_with_songpos(self): seek_track = Track(uri='2', length=40000) self.backend.current_playlist.append( [Track(uri='1', length=40000), seek_track]) - result = self.dispatcher.handle_request(u'seek "1" "30"') - self.assert_(u'OK' in result) + + self.sendRequest(u'seek "1" "30"') self.assertEqual(self.backend.playback.current_track.get(), seek_track) + self.assertResponse(u'OK') def test_seek_without_quotes(self): self.backend.current_playlist.append([Track(length=40000)]) - self.dispatcher.handle_request(u'seek 0') - result = self.dispatcher.handle_request(u'seek 0 30') - self.assert_(u'OK' in result) + + self.sendRequest(u'seek 0') + self.sendRequest(u'seek 0 30') self.assert_(self.backend.playback.time_position.get() >= 30000) + self.assertResponse(u'OK') def test_seekid(self): self.backend.current_playlist.append([Track(length=40000)]) - result = self.dispatcher.handle_request(u'seekid "0" "30"') - self.assert_(u'OK' in result) + self.sendRequest(u'seekid "0" "30"') self.assert_(self.backend.playback.time_position.get() >= 30000) + self.assertResponse(u'OK') def test_seekid_with_cpid(self): seek_track = Track(uri='2', length=40000) self.backend.current_playlist.append( [Track(length=40000), seek_track]) - result = self.dispatcher.handle_request(u'seekid "1" "30"') - self.assert_(u'OK' in result) - self.assertEqual(self.backend.playback.current_cpid.get(), 1) - self.assertEqual(self.backend.playback.current_track.get(), seek_track) + + self.sendRequest(u'seekid "1" "30"') + self.assertEqual(1, self.backend.playback.current_cpid.get()) + self.assertEqual(seek_track, self.backend.playback.current_track.get()) + self.assertResponse(u'OK') def test_stop(self): - result = self.dispatcher.handle_request(u'stop') - self.assert_(u'OK' in result) + self.sendRequest(u'stop') self.assertEqual(STOPPED, self.backend.playback.state.get()) + self.assertResponse(u'OK') From 3d1c47586ed17e9b0cd411299c29caf7fcafdc31 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Wed, 20 Jul 2011 16:14:43 +0200 Subject: [PATCH 11/43] Match assertIn behaviour and rename helper to assertInResponse --- tests/frontends/mpd/protocol/__init__.py | 8 +- tests/frontends/mpd/protocol/playback_test.py | 116 +++++++++--------- 2 files changed, 61 insertions(+), 63 deletions(-) diff --git a/tests/frontends/mpd/protocol/__init__.py b/tests/frontends/mpd/protocol/__init__.py index a3705c48..5b3eabf2 100644 --- a/tests/frontends/mpd/protocol/__init__.py +++ b/tests/frontends/mpd/protocol/__init__.py @@ -36,8 +36,6 @@ class BaseTestCase(unittest.TestCase): self.connection.response = [] self.session.on_line_received(request) - def assertResponse(self, value, index=None): - if index is not None: - self.assertEqual(value, self.connection.response[index]) - else: - self.assert_(value in self.connection.response) + def assertInResponse(self, value): + self.assert_(value in self.connection.response, u'Did not find %s ' + 'in %s' % (repr(value), repr(self.connection.response))) diff --git a/tests/frontends/mpd/protocol/playback_test.py b/tests/frontends/mpd/protocol/playback_test.py index b9129688..ce561108 100644 --- a/tests/frontends/mpd/protocol/playback_test.py +++ b/tests/frontends/mpd/protocol/playback_test.py @@ -13,138 +13,138 @@ class PlaybackOptionsHandlerTest(protocol.BaseTestCase): def test_consume_off(self): self.sendRequest(u'consume "0"') self.assertFalse(self.backend.playback.consume.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_consume_off_without_quotes(self): self.sendRequest(u'consume 0') self.assertFalse(self.backend.playback.consume.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_consume_on(self): self.sendRequest(u'consume "1"') self.assertTrue(self.backend.playback.consume.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_consume_on_without_quotes(self): self.sendRequest(u'consume 1') self.assertTrue(self.backend.playback.consume.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_crossfade(self): self.sendRequest(u'crossfade "10"') - self.assertResponse(u'ACK [0@0] {} Not implemented') + self.assertInResponse(u'ACK [0@0] {} Not implemented') def test_random_off(self): self.sendRequest(u'random "0"') self.assertFalse(self.backend.playback.random.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_random_off_without_quotes(self): self.sendRequest(u'random 0') self.assertFalse(self.backend.playback.random.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_random_on(self): self.sendRequest(u'random "1"') self.assertTrue(self.backend.playback.random.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_random_on_without_quotes(self): self.sendRequest(u'random 1') self.assertTrue(self.backend.playback.random.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_repeat_off(self): self.sendRequest(u'repeat "0"') self.assertFalse(self.backend.playback.repeat.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_repeat_off_without_quotes(self): self.sendRequest(u'repeat 0') self.assertFalse(self.backend.playback.repeat.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_repeat_on(self): self.sendRequest(u'repeat "1"') self.assertTrue(self.backend.playback.repeat.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_repeat_on_without_quotes(self): self.sendRequest(u'repeat 1') self.assertTrue(self.backend.playback.repeat.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_setvol_below_min(self): self.sendRequest(u'setvol "-10"') self.assertEqual(0, self.mixer.volume.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_setvol_min(self): self.sendRequest(u'setvol "0"') self.assertEqual(0, self.mixer.volume.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_setvol_middle(self): self.sendRequest(u'setvol "50"') self.assertEqual(50, self.mixer.volume.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_setvol_max(self): self.sendRequest(u'setvol "100"') self.assertEqual(100, self.mixer.volume.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_setvol_above_max(self): self.sendRequest(u'setvol "110"') self.assertEqual(100, self.mixer.volume.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_setvol_plus_is_ignored(self): self.sendRequest(u'setvol "+10"') self.assertEqual(10, self.mixer.volume.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_setvol_without_quotes(self): self.sendRequest(u'setvol 50') self.assertEqual(50, self.mixer.volume.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_single_off(self): self.sendRequest(u'single "0"') self.assertFalse(self.backend.playback.single.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_single_off_without_quotes(self): self.sendRequest(u'single 0') self.assertFalse(self.backend.playback.single.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_single_on(self): self.sendRequest(u'single "1"') self.assertTrue(self.backend.playback.single.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_single_on_without_quotes(self): self.sendRequest(u'single 1') self.assertTrue(self.backend.playback.single.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_replay_gain_mode_off(self): self.sendRequest(u'replay_gain_mode "off"') - self.assertResponse(u'ACK [0@0] {} Not implemented') + self.assertInResponse(u'ACK [0@0] {} Not implemented') def test_replay_gain_mode_track(self): self.sendRequest(u'replay_gain_mode "track"') - self.assertResponse(u'ACK [0@0] {} Not implemented') + self.assertInResponse(u'ACK [0@0] {} Not implemented') def test_replay_gain_mode_album(self): self.sendRequest(u'replay_gain_mode "album"') - self.assertResponse(u'ACK [0@0] {} Not implemented') + self.assertInResponse(u'ACK [0@0] {} Not implemented') def test_replay_gain_status_default(self): self.sendRequest(u'replay_gain_status') - self.assertResponse(u'OK') - self.assertResponse(u'off') + self.assertInResponse(u'OK') + self.assertInResponse(u'off') def test_replay_gain_status_off(self): raise SkipTest # TODO @@ -159,7 +159,7 @@ class PlaybackOptionsHandlerTest(protocol.BaseTestCase): class PlaybackControlHandlerTest(protocol.BaseTestCase): def test_next(self): self.sendRequest(u'next') - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_pause_off(self): self.backend.current_playlist.append([Track()]) @@ -168,7 +168,7 @@ class PlaybackControlHandlerTest(protocol.BaseTestCase): self.sendRequest(u'pause "1"') self.sendRequest(u'pause "0"') self.assertEqual(PLAYING, self.backend.playback.state.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_pause_on(self): self.backend.current_playlist.append([Track()]) @@ -176,22 +176,22 @@ class PlaybackControlHandlerTest(protocol.BaseTestCase): self.sendRequest(u'play "0"') self.sendRequest(u'pause "1"') self.assertEqual(PAUSED, self.backend.playback.state.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_pause_toggle(self): self.backend.current_playlist.append([Track()]) self.sendRequest(u'play "0"') self.assertEqual(PLAYING, self.backend.playback.state.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') self.sendRequest(u'pause') self.assertEqual(PAUSED, self.backend.playback.state.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') self.sendRequest(u'pause') self.assertEqual(PLAYING, self.backend.playback.state.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_play_without_pos(self): self.backend.current_playlist.append([Track()]) @@ -199,28 +199,28 @@ class PlaybackControlHandlerTest(protocol.BaseTestCase): self.sendRequest(u'play') self.assertEqual(PLAYING, self.backend.playback.state.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_play_with_pos(self): self.backend.current_playlist.append([Track()]) self.sendRequest(u'play "0"') self.assertEqual(PLAYING, self.backend.playback.state.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_play_with_pos_without_quotes(self): self.backend.current_playlist.append([Track()]) self.sendRequest(u'play 0') self.assertEqual(PLAYING, self.backend.playback.state.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_play_with_pos_out_of_bounds(self): self.backend.current_playlist.append([]) self.sendRequest(u'play "0"') self.assertEqual(STOPPED, self.backend.playback.state.get()) - self.assertResponse(u'ACK [2@0] {play} Bad song index', index=0) + self.assertInResponse(u'ACK [2@0] {play} Bad song index') def test_play_minus_one_plays_first_in_playlist_if_no_current_track(self): self.assertEqual(self.backend.playback.current_track.get(), None) @@ -229,7 +229,7 @@ class PlaybackControlHandlerTest(protocol.BaseTestCase): self.sendRequest(u'play "-1"') self.assertEqual(PLAYING, self.backend.playback.state.get()) self.assertEqual('a', self.backend.playback.current_track.get().uri) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_play_minus_one_plays_current_track_if_current_track_is_set(self): self.backend.current_playlist.append([Track(uri='a'), Track(uri='b')]) @@ -242,7 +242,7 @@ class PlaybackControlHandlerTest(protocol.BaseTestCase): self.sendRequest(u'play "-1"') self.assertEqual(PLAYING, self.backend.playback.state.get()) self.assertEqual('b', self.backend.playback.current_track.get().uri) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_play_minus_one_on_empty_playlist_does_not_ack(self): self.backend.current_playlist.clear() @@ -250,7 +250,7 @@ class PlaybackControlHandlerTest(protocol.BaseTestCase): self.sendRequest(u'play "-1"') self.assertEqual(STOPPED, self.backend.playback.state.get()) self.assertEqual(None, self.backend.playback.current_track.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_play_minus_is_ignored_if_playing(self): self.backend.current_playlist.append([Track(length=40000)]) @@ -261,7 +261,7 @@ class PlaybackControlHandlerTest(protocol.BaseTestCase): self.sendRequest(u'play "-1"') self.assertEqual(PLAYING, self.backend.playback.state.get()) self.assert_(self.backend.playback.time_position.get() >= 30000) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_play_minus_one_resumes_if_paused(self): self.backend.current_playlist.append([Track(length=40000)]) @@ -274,14 +274,14 @@ class PlaybackControlHandlerTest(protocol.BaseTestCase): self.sendRequest(u'play "-1"') self.assertEqual(PLAYING, self.backend.playback.state.get()) self.assert_(self.backend.playback.time_position.get() >= 30000) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_playid(self): self.backend.current_playlist.append([Track()]) self.sendRequest(u'playid "0"') self.assertEqual(PLAYING, self.backend.playback.state.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_playid_minus_one_plays_first_in_playlist_if_no_current_track(self): self.assertEqual(self.backend.playback.current_track.get(), None) @@ -290,7 +290,7 @@ class PlaybackControlHandlerTest(protocol.BaseTestCase): self.sendRequest(u'playid "-1"') self.assertEqual(PLAYING, self.backend.playback.state.get()) self.assertEqual('a', self.backend.playback.current_track.get().uri) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_playid_minus_one_plays_current_track_if_current_track_is_set(self): self.backend.current_playlist.append([Track(uri='a'), Track(uri='b')]) @@ -303,7 +303,7 @@ class PlaybackControlHandlerTest(protocol.BaseTestCase): self.sendRequest(u'playid "-1"') self.assertEqual(PLAYING, self.backend.playback.state.get()) self.assertEqual('b', self.backend.playback.current_track.get().uri) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_playid_minus_one_on_empty_playlist_does_not_ack(self): self.backend.current_playlist.clear() @@ -311,7 +311,7 @@ class PlaybackControlHandlerTest(protocol.BaseTestCase): self.sendRequest(u'playid "-1"') self.assertEqual(STOPPED, self.backend.playback.state.get()) self.assertEqual(None, self.backend.playback.current_track.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_playid_minus_is_ignored_if_playing(self): self.backend.current_playlist.append([Track(length=40000)]) @@ -322,7 +322,7 @@ class PlaybackControlHandlerTest(protocol.BaseTestCase): self.sendRequest(u'playid "-1"') self.assertEqual(PLAYING, self.backend.playback.state.get()) self.assert_(self.backend.playback.time_position.get() >= 30000) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_playid_minus_one_resumes_if_paused(self): self.backend.current_playlist.append([Track(length=40000)]) @@ -335,17 +335,17 @@ class PlaybackControlHandlerTest(protocol.BaseTestCase): self.sendRequest(u'playid "-1"') self.assertEqual(PLAYING, self.backend.playback.state.get()) self.assert_(self.backend.playback.time_position.get() >= 30000) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_playid_which_does_not_exist(self): self.backend.current_playlist.append([Track()]) self.sendRequest(u'playid "12345"') - self.assertResponse(u'ACK [50@0] {playid} No such song', index=0) + self.assertInResponse(u'ACK [50@0] {playid} No such song') def test_previous(self): self.sendRequest(u'previous') - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_seek(self): self.backend.current_playlist.append([Track(length=40000)]) @@ -353,7 +353,7 @@ class PlaybackControlHandlerTest(protocol.BaseTestCase): self.sendRequest(u'seek "0"') self.sendRequest(u'seek "0" "30"') self.assert_(self.backend.playback.time_position >= 30000) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_seek_with_songpos(self): seek_track = Track(uri='2', length=40000) @@ -362,7 +362,7 @@ class PlaybackControlHandlerTest(protocol.BaseTestCase): self.sendRequest(u'seek "1" "30"') self.assertEqual(self.backend.playback.current_track.get(), seek_track) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_seek_without_quotes(self): self.backend.current_playlist.append([Track(length=40000)]) @@ -370,13 +370,13 @@ class PlaybackControlHandlerTest(protocol.BaseTestCase): self.sendRequest(u'seek 0') self.sendRequest(u'seek 0 30') self.assert_(self.backend.playback.time_position.get() >= 30000) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_seekid(self): self.backend.current_playlist.append([Track(length=40000)]) self.sendRequest(u'seekid "0" "30"') self.assert_(self.backend.playback.time_position.get() >= 30000) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_seekid_with_cpid(self): seek_track = Track(uri='2', length=40000) @@ -386,9 +386,9 @@ class PlaybackControlHandlerTest(protocol.BaseTestCase): self.sendRequest(u'seekid "1" "30"') self.assertEqual(1, self.backend.playback.current_cpid.get()) self.assertEqual(seek_track, self.backend.playback.current_track.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') def test_stop(self): self.sendRequest(u'stop') self.assertEqual(STOPPED, self.backend.playback.state.get()) - self.assertResponse(u'OK') + self.assertInResponse(u'OK') From 6d444362a15fb3b7b0f7c85d0e155783a3abe2b4 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Wed, 20 Jul 2011 16:25:16 +0200 Subject: [PATCH 12/43] Cleanup imports in playback_test --- tests/frontends/mpd/protocol/playback_test.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/frontends/mpd/protocol/playback_test.py b/tests/frontends/mpd/protocol/playback_test.py index ce561108..6f93dc72 100644 --- a/tests/frontends/mpd/protocol/playback_test.py +++ b/tests/frontends/mpd/protocol/playback_test.py @@ -1,12 +1,12 @@ -from mopidy.backends.base import PlaybackController +from mopidy.backends import base as backend from mopidy.models import Track from tests import SkipTest from tests.frontends.mpd import protocol -PAUSED = PlaybackController.PAUSED -PLAYING = PlaybackController.PLAYING -STOPPED = PlaybackController.STOPPED +PAUSED = backend.PlaybackController.PAUSED +PLAYING = backend.PlaybackController.PLAYING +STOPPED = backend.PlaybackController.STOPPED class PlaybackOptionsHandlerTest(protocol.BaseTestCase): From 523f5eb03a467d9c5b5feaf67a65fd766edc95a4 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Wed, 20 Jul 2011 16:29:39 +0200 Subject: [PATCH 13/43] Migrate audo_output_test --- tests/frontends/mpd/audio_output_test.py | 30 ------------------- .../mpd/protocol/audio_output_test.py | 17 +++++++++++ 2 files changed, 17 insertions(+), 30 deletions(-) delete mode 100644 tests/frontends/mpd/audio_output_test.py create mode 100644 tests/frontends/mpd/protocol/audio_output_test.py diff --git a/tests/frontends/mpd/audio_output_test.py b/tests/frontends/mpd/audio_output_test.py deleted file mode 100644 index 82d9e203..00000000 --- a/tests/frontends/mpd/audio_output_test.py +++ /dev/null @@ -1,30 +0,0 @@ -import unittest - -from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpd.dispatcher import MpdDispatcher -from mopidy.mixers.dummy import DummyMixer - -class AudioOutputHandlerTest(unittest.TestCase): - def setUp(self): - self.backend = DummyBackend.start().proxy() - self.mixer = DummyMixer.start().proxy() - self.dispatcher = MpdDispatcher() - - def tearDown(self): - self.backend.stop().get() - self.mixer.stop().get() - - def test_enableoutput(self): - result = self.dispatcher.handle_request(u'enableoutput "0"') - self.assert_(u'ACK [0@0] {} Not implemented' in result) - - def test_disableoutput(self): - result = self.dispatcher.handle_request(u'disableoutput "0"') - self.assert_(u'ACK [0@0] {} Not implemented' in result) - - def test_outputs(self): - result = self.dispatcher.handle_request(u'outputs') - self.assert_(u'outputid: 0' in result) - self.assert_(u'outputname: None' in result) - self.assert_(u'outputenabled: 1' in result) - self.assert_(u'OK' in result) diff --git a/tests/frontends/mpd/protocol/audio_output_test.py b/tests/frontends/mpd/protocol/audio_output_test.py new file mode 100644 index 00000000..f9374159 --- /dev/null +++ b/tests/frontends/mpd/protocol/audio_output_test.py @@ -0,0 +1,17 @@ +from tests.frontends.mpd import protocol + +class AudioOutputHandlerTest(protocol.BaseTestCase): + def test_enableoutput(self): + self.sendRequest(u'enableoutput "0"') + self.assertInResponse(u'ACK [0@0] {} Not implemented') + + def test_disableoutput(self): + self.sendRequest(u'disableoutput "0"') + self.assertInResponse(u'ACK [0@0] {} Not implemented') + + def test_outputs(self): + self.sendRequest(u'outputs') + self.assertInResponse(u'outputid: 0') + self.assertInResponse(u'outputname: None') + self.assertInResponse(u'outputenabled: 1') + self.assertInResponse(u'OK') From c4a1692d92290a61df87439f392cfccf1ba68b27 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Wed, 20 Jul 2011 17:32:33 +0200 Subject: [PATCH 14/43] Migrate current_playlist_test --- tests/frontends/mpd/protocol/__init__.py | 12 +- .../{ => protocol}/current_playlist_test.py | 351 +++++++++--------- 2 files changed, 192 insertions(+), 171 deletions(-) rename tests/frontends/mpd/{ => protocol}/current_playlist_test.py (59%) diff --git a/tests/frontends/mpd/protocol/__init__.py b/tests/frontends/mpd/protocol/__init__.py index 5b3eabf2..fd56c32e 100644 --- a/tests/frontends/mpd/protocol/__init__.py +++ b/tests/frontends/mpd/protocol/__init__.py @@ -16,7 +16,8 @@ class MockConnetion(mock.Mock): self.response = [] def send(self, data): - self.response.extend(data.split('\n')) + lines = (line for line in data.split('\n') if line) + self.response.extend(lines) class BaseTestCase(unittest.TestCase): @@ -35,7 +36,16 @@ class BaseTestCase(unittest.TestCase): def sendRequest(self, request, clear=False): self.connection.response = [] self.session.on_line_received(request) + return self.connection.response def assertInResponse(self, value): self.assert_(value in self.connection.response, u'Did not find %s ' 'in %s' % (repr(value), repr(self.connection.response))) + + def assertNotInResponse(self, value): + self.assert_(value not in self.connection.response, u'Found %s in %s' % + (repr(value), repr(self.connection.response))) + + def assertEqualResponse(self, value): + self.assertEqual(1, len(self.connection.response)) + self.assertEqual(value, self.connection.response[0]) diff --git a/tests/frontends/mpd/current_playlist_test.py b/tests/frontends/mpd/protocol/current_playlist_test.py similarity index 59% rename from tests/frontends/mpd/current_playlist_test.py rename to tests/frontends/mpd/protocol/current_playlist_test.py index c7f47429..1b0ae404 100644 --- a/tests/frontends/mpd/current_playlist_test.py +++ b/tests/frontends/mpd/protocol/current_playlist_test.py @@ -1,20 +1,8 @@ -import unittest - -from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpd.dispatcher import MpdDispatcher -from mopidy.mixers.dummy import DummyMixer from mopidy.models import Track -class CurrentPlaylistHandlerTest(unittest.TestCase): - def setUp(self): - self.backend = DummyBackend.start().proxy() - self.mixer = DummyMixer.start().proxy() - self.dispatcher = MpdDispatcher() - - def tearDown(self): - self.backend.stop().get() - self.mixer.stop().get() +from tests.frontends.mpd import protocol +class CurrentPlaylistHandlerTest(protocol.BaseTestCase): def test_add(self): needle = Track(uri='dummy://foo') self.backend.library.provider.dummy_library = [ @@ -22,21 +10,21 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): self.backend.current_playlist.append( [Track(), Track(), Track(), Track(), Track()]) self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5) - result = self.dispatcher.handle_request(u'add "dummy://foo"') - self.assertEqual(len(result), 1) - self.assertEqual(result[0], u'OK') + + self.sendRequest(u'add "dummy://foo"') self.assertEqual(len(self.backend.current_playlist.tracks.get()), 6) self.assertEqual(self.backend.current_playlist.tracks.get()[5], needle) + self.assertEqualResponse(u'OK') def test_add_with_uri_not_found_in_library_should_ack(self): - result = self.dispatcher.handle_request(u'add "dummy://foo"') - self.assertEqual(result[0], + self.sendRequest(u'add "dummy://foo"') + self.assertEqualResponse( u'ACK [50@0] {add} directory or file not found') def test_add_with_empty_uri_should_add_all_known_tracks_and_ok(self): - result = self.dispatcher.handle_request(u'add ""') + self.sendRequest(u'add ""') # TODO check that we add all tracks (we currently don't) - self.assert_(u'OK' in result) + self.assertInResponse(u'OK') def test_addid_without_songpos(self): needle = Track(uri='dummy://foo') @@ -45,16 +33,17 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): self.backend.current_playlist.append( [Track(), Track(), Track(), Track(), Track()]) self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5) - result = self.dispatcher.handle_request(u'addid "dummy://foo"') + + self.sendRequest(u'addid "dummy://foo"') self.assertEqual(len(self.backend.current_playlist.tracks.get()), 6) self.assertEqual(self.backend.current_playlist.tracks.get()[5], needle) - self.assert_(u'Id: %d' % - self.backend.current_playlist.cp_tracks.get()[5][0] in result) - self.assert_(u'OK' in result) + self.assertInResponse(u'Id: %d' % + self.backend.current_playlist.cp_tracks.get()[5][0]) + self.assertInResponse(u'OK') def test_addid_with_empty_uri_acks(self): - result = self.dispatcher.handle_request(u'addid ""') - self.assertEqual(result[0], u'ACK [50@0] {addid} No such song') + self.sendRequest(u'addid ""') + self.assertEqualResponse(u'ACK [50@0] {addid} No such song') def test_addid_with_songpos(self): needle = Track(uri='dummy://foo') @@ -63,12 +52,13 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): self.backend.current_playlist.append( [Track(), Track(), Track(), Track(), Track()]) self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5) - result = self.dispatcher.handle_request(u'addid "dummy://foo" "3"') + + self.sendRequest(u'addid "dummy://foo" "3"') self.assertEqual(len(self.backend.current_playlist.tracks.get()), 6) self.assertEqual(self.backend.current_playlist.tracks.get()[3], needle) - self.assert_(u'Id: %d' % - self.backend.current_playlist.cp_tracks.get()[3][0] in result) - self.assert_(u'OK' in result) + self.assertInResponse(u'Id: %d' % + self.backend.current_playlist.cp_tracks.get()[3][0]) + self.assertInResponse(u'OK') def test_addid_with_songpos_out_of_bounds_should_ack(self): needle = Track(uri='dummy://foo') @@ -77,83 +67,93 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): self.backend.current_playlist.append( [Track(), Track(), Track(), Track(), Track()]) self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5) - result = self.dispatcher.handle_request(u'addid "dummy://foo" "6"') - self.assertEqual(result[0], u'ACK [2@0] {addid} Bad song index') + + self.sendRequest(u'addid "dummy://foo" "6"') + self.assertEqualResponse(u'ACK [2@0] {addid} Bad song index') def test_addid_with_uri_not_found_in_library_should_ack(self): - result = self.dispatcher.handle_request(u'addid "dummy://foo"') - self.assertEqual(result[0], u'ACK [50@0] {addid} No such song') + self.sendRequest(u'addid "dummy://foo"') + self.assertEqualResponse(u'ACK [50@0] {addid} No such song') def test_clear(self): self.backend.current_playlist.append( [Track(), Track(), Track(), Track(), Track()]) self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5) - result = self.dispatcher.handle_request(u'clear') + + self.sendRequest(u'clear') self.assertEqual(len(self.backend.current_playlist.tracks.get()), 0) self.assertEqual(self.backend.playback.current_track.get(), None) - self.assert_(u'OK' in result) + self.assertInResponse(u'OK') def test_delete_songpos(self): self.backend.current_playlist.append( [Track(), Track(), Track(), Track(), Track()]) self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5) - result = self.dispatcher.handle_request(u'delete "%d"' % + + self.sendRequest(u'delete "%d"' % self.backend.current_playlist.cp_tracks.get()[2][0]) self.assertEqual(len(self.backend.current_playlist.tracks.get()), 4) - self.assert_(u'OK' in result) + self.assertInResponse(u'OK') def test_delete_songpos_out_of_bounds(self): self.backend.current_playlist.append( [Track(), Track(), Track(), Track(), Track()]) self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5) - result = self.dispatcher.handle_request(u'delete "5"') + + self.sendRequest(u'delete "5"') self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5) - self.assertEqual(result[0], u'ACK [2@0] {delete} Bad song index') + self.assertEqualResponse(u'ACK [2@0] {delete} Bad song index') def test_delete_open_range(self): self.backend.current_playlist.append( [Track(), Track(), Track(), Track(), Track()]) self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5) - result = self.dispatcher.handle_request(u'delete "1:"') + + self.sendRequest(u'delete "1:"') self.assertEqual(len(self.backend.current_playlist.tracks.get()), 1) - self.assert_(u'OK' in result) + self.assertInResponse(u'OK') def test_delete_closed_range(self): self.backend.current_playlist.append( [Track(), Track(), Track(), Track(), Track()]) self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5) - result = self.dispatcher.handle_request(u'delete "1:3"') + + self.sendRequest(u'delete "1:3"') self.assertEqual(len(self.backend.current_playlist.tracks.get()), 3) - self.assert_(u'OK' in result) + self.assertInResponse(u'OK') def test_delete_range_out_of_bounds(self): self.backend.current_playlist.append( [Track(), Track(), Track(), Track(), Track()]) self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5) - result = self.dispatcher.handle_request(u'delete "5:7"') + + self.sendRequest(u'delete "5:7"') self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5) - self.assertEqual(result[0], u'ACK [2@0] {delete} Bad song index') + self.assertEqualResponse(u'ACK [2@0] {delete} Bad song index') def test_deleteid(self): self.backend.current_playlist.append([Track(), Track()]) self.assertEqual(len(self.backend.current_playlist.tracks.get()), 2) - result = self.dispatcher.handle_request(u'deleteid "1"') + + self.sendRequest(u'deleteid "1"') self.assertEqual(len(self.backend.current_playlist.tracks.get()), 1) - self.assert_(u'OK' in result) + self.assertInResponse(u'OK') def test_deleteid_does_not_exist(self): self.backend.current_playlist.append([Track(), Track()]) self.assertEqual(len(self.backend.current_playlist.tracks.get()), 2) - result = self.dispatcher.handle_request(u'deleteid "12345"') + + self.sendRequest(u'deleteid "12345"') self.assertEqual(len(self.backend.current_playlist.tracks.get()), 2) - self.assertEqual(result[0], u'ACK [50@0] {deleteid} No such song') + self.assertEqualResponse(u'ACK [50@0] {deleteid} No such song') def test_move_songpos(self): self.backend.current_playlist.append([ Track(name='a'), Track(name='b'), Track(name='c'), Track(name='d'), Track(name='e'), Track(name='f'), ]) - result = self.dispatcher.handle_request(u'move "1" "0"') + + self.sendRequest(u'move "1" "0"') tracks = self.backend.current_playlist.tracks.get() self.assertEqual(tracks[0].name, 'b') self.assertEqual(tracks[1].name, 'a') @@ -161,14 +161,15 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): self.assertEqual(tracks[3].name, 'd') self.assertEqual(tracks[4].name, 'e') self.assertEqual(tracks[5].name, 'f') - self.assert_(u'OK' in result) + self.assertInResponse(u'OK') def test_move_open_range(self): self.backend.current_playlist.append([ Track(name='a'), Track(name='b'), Track(name='c'), Track(name='d'), Track(name='e'), Track(name='f'), ]) - result = self.dispatcher.handle_request(u'move "2:" "0"') + + self.sendRequest(u'move "2:" "0"') tracks = self.backend.current_playlist.tracks.get() self.assertEqual(tracks[0].name, 'c') self.assertEqual(tracks[1].name, 'd') @@ -176,14 +177,15 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): self.assertEqual(tracks[3].name, 'f') self.assertEqual(tracks[4].name, 'a') self.assertEqual(tracks[5].name, 'b') - self.assert_(u'OK' in result) + self.assertInResponse(u'OK') def test_move_closed_range(self): self.backend.current_playlist.append([ Track(name='a'), Track(name='b'), Track(name='c'), Track(name='d'), Track(name='e'), Track(name='f'), ]) - result = self.dispatcher.handle_request(u'move "1:3" "0"') + + self.sendRequest(u'move "1:3" "0"') tracks = self.backend.current_playlist.tracks.get() self.assertEqual(tracks[0].name, 'b') self.assertEqual(tracks[1].name, 'c') @@ -191,14 +193,15 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): self.assertEqual(tracks[3].name, 'd') self.assertEqual(tracks[4].name, 'e') self.assertEqual(tracks[5].name, 'f') - self.assert_(u'OK' in result) + self.assertInResponse(u'OK') def test_moveid(self): self.backend.current_playlist.append([ Track(name='a'), Track(name='b'), Track(name='c'), Track(name='d'), Track(name='e'), Track(name='f'), ]) - result = self.dispatcher.handle_request(u'moveid "4" "2"') + + self.sendRequest(u'moveid "4" "2"') tracks = self.backend.current_playlist.tracks.get() self.assertEqual(tracks[0].name, 'a') self.assertEqual(tracks[1].name, 'b') @@ -206,179 +209,182 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): self.assertEqual(tracks[3].name, 'c') self.assertEqual(tracks[4].name, 'd') self.assertEqual(tracks[5].name, 'f') - self.assert_(u'OK' in result) + self.assertInResponse(u'OK') def test_playlist_returns_same_as_playlistinfo(self): - playlist_result = self.dispatcher.handle_request(u'playlist') - playlistinfo_result = self.dispatcher.handle_request(u'playlistinfo') - self.assertEqual(playlist_result, playlistinfo_result) + playlist_response = self.sendRequest(u'playlist') + playlistinfo_response = self.sendRequest(u'playlistinfo') + self.assertEqual(playlist_response, playlistinfo_response) def test_playlistfind(self): - result = self.dispatcher.handle_request(u'playlistfind "tag" "needle"') - self.assert_(u'ACK [0@0] {} Not implemented' in result) + self.sendRequest(u'playlistfind "tag" "needle"') + self.assertEqualResponse(u'ACK [0@0] {} Not implemented') def test_playlistfind_by_filename_not_in_current_playlist(self): - result = self.dispatcher.handle_request( - u'playlistfind "filename" "file:///dev/null"') - self.assertEqual(len(result), 1) - self.assert_(u'OK' in result) + self.sendRequest(u'playlistfind "filename" "file:///dev/null"') + self.assertEqualResponse(u'OK') def test_playlistfind_by_filename_without_quotes(self): - result = self.dispatcher.handle_request( - u'playlistfind filename "file:///dev/null"') - self.assertEqual(len(result), 1) - self.assert_(u'OK' in result) + self.sendRequest(u'playlistfind filename "file:///dev/null"') + self.assertEqualResponse(u'OK') def test_playlistfind_by_filename_in_current_playlist(self): self.backend.current_playlist.append([ Track(uri='file:///exists')]) - result = self.dispatcher.handle_request( - u'playlistfind filename "file:///exists"') - self.assert_(u'file: file:///exists' in result) - self.assert_(u'Id: 0' in result) - self.assert_(u'Pos: 0' in result) - self.assert_(u'OK' in result) + + self.sendRequest( u'playlistfind filename "file:///exists"') + self.assertInResponse(u'file: file:///exists') + self.assertInResponse(u'Id: 0') + self.assertInResponse(u'Pos: 0') + self.assertInResponse(u'OK') def test_playlistid_without_songid(self): self.backend.current_playlist.append([Track(name='a'), Track(name='b')]) - result = self.dispatcher.handle_request(u'playlistid') - self.assert_(u'Title: a' in result) - self.assert_(u'Title: b' in result) - self.assert_(u'OK' in result) + + self.sendRequest(u'playlistid') + self.assertInResponse(u'Title: a') + self.assertInResponse(u'Title: b') + self.assertInResponse(u'OK') def test_playlistid_with_songid(self): self.backend.current_playlist.append([Track(name='a'), Track(name='b')]) - result = self.dispatcher.handle_request(u'playlistid "1"') - self.assert_(u'Title: a' not in result) - self.assert_(u'Id: 0' not in result) - self.assert_(u'Title: b' in result) - self.assert_(u'Id: 1' in result) - self.assert_(u'OK' in result) + + self.sendRequest(u'playlistid "1"') + self.assertNotInResponse(u'Title: a') + self.assertNotInResponse(u'Id: 0') + self.assertInResponse(u'Title: b') + self.assertInResponse(u'Id: 1') + self.assertInResponse(u'OK') def test_playlistid_with_not_existing_songid_fails(self): self.backend.current_playlist.append([Track(name='a'), Track(name='b')]) - result = self.dispatcher.handle_request(u'playlistid "25"') - self.assertEqual(result[0], u'ACK [50@0] {playlistid} No such song') + + self.sendRequest(u'playlistid "25"') + self.assertEqualResponse(u'ACK [50@0] {playlistid} No such song') def test_playlistinfo_without_songpos_or_range(self): self.backend.current_playlist.append([ Track(name='a'), Track(name='b'), Track(name='c'), Track(name='d'), Track(name='e'), Track(name='f'), ]) - result = self.dispatcher.handle_request(u'playlistinfo') - self.assert_(u'Title: a' in result) - self.assert_(u'Title: b' in result) - self.assert_(u'Title: c' in result) - self.assert_(u'Title: d' in result) - self.assert_(u'Title: e' in result) - self.assert_(u'Title: f' in result) - self.assert_(u'OK' in result) + + self.sendRequest(u'playlistinfo') + self.assertInResponse(u'Title: a') + self.assertInResponse(u'Title: b') + self.assertInResponse(u'Title: c') + self.assertInResponse(u'Title: d') + self.assertInResponse(u'Title: e') + self.assertInResponse(u'Title: f') + self.assertInResponse(u'OK') def test_playlistinfo_with_songpos(self): self.backend.current_playlist.append([ Track(name='a'), Track(name='b'), Track(name='c'), Track(name='d'), Track(name='e'), Track(name='f'), ]) - result = self.dispatcher.handle_request(u'playlistinfo "4"') - self.assert_(u'Title: a' not in result) - self.assert_(u'Title: b' not in result) - self.assert_(u'Title: c' not in result) - self.assert_(u'Title: d' not in result) - self.assert_(u'Title: e' in result) - self.assert_(u'Title: f' not in result) - self.assert_(u'OK' in result) + + self.sendRequest(u'playlistinfo "4"') + self.assertNotInResponse(u'Title: a') + self.assertNotInResponse(u'Title: b') + self.assertNotInResponse(u'Title: c') + self.assertNotInResponse(u'Title: d') + self.assertInResponse(u'Title: e') + self.assertNotInResponse(u'Title: f') + self.assertInResponse(u'OK') def test_playlistinfo_with_negative_songpos_same_as_playlistinfo(self): - result1 = self.dispatcher.handle_request(u'playlistinfo "-1"') - result2 = self.dispatcher.handle_request(u'playlistinfo') - self.assertEqual(result1, result2) + response1 = self.sendRequest(u'playlistinfo "-1"') + response2 = self.sendRequest(u'playlistinfo') + self.assertEqual(response1, response2) def test_playlistinfo_with_open_range(self): self.backend.current_playlist.append([ Track(name='a'), Track(name='b'), Track(name='c'), Track(name='d'), Track(name='e'), Track(name='f'), ]) - result = self.dispatcher.handle_request(u'playlistinfo "2:"') - self.assert_(u'Title: a' not in result) - self.assert_(u'Title: b' not in result) - self.assert_(u'Title: c' in result) - self.assert_(u'Title: d' in result) - self.assert_(u'Title: e' in result) - self.assert_(u'Title: f' in result) - self.assert_(u'OK' in result) + + self.sendRequest(u'playlistinfo "2:"') + self.assertNotInResponse(u'Title: a') + self.assertNotInResponse(u'Title: b') + self.assertInResponse(u'Title: c') + self.assertInResponse(u'Title: d') + self.assertInResponse(u'Title: e') + self.assertInResponse(u'Title: f') + self.assertInResponse(u'OK') def test_playlistinfo_with_closed_range(self): self.backend.current_playlist.append([ Track(name='a'), Track(name='b'), Track(name='c'), Track(name='d'), Track(name='e'), Track(name='f'), ]) - result = self.dispatcher.handle_request(u'playlistinfo "2:4"') - self.assert_(u'Title: a' not in result) - self.assert_(u'Title: b' not in result) - self.assert_(u'Title: c' in result) - self.assert_(u'Title: d' in result) - self.assert_(u'Title: e' not in result) - self.assert_(u'Title: f' not in result) - self.assert_(u'OK' in result) + + self.sendRequest(u'playlistinfo "2:4"') + self.assertNotInResponse(u'Title: a') + self.assertNotInResponse(u'Title: b') + self.assertInResponse(u'Title: c') + self.assertInResponse(u'Title: d') + self.assertNotInResponse(u'Title: e') + self.assertNotInResponse(u'Title: f') + self.assertInResponse(u'OK') def test_playlistinfo_with_too_high_start_of_range_returns_arg_error(self): - result = self.dispatcher.handle_request(u'playlistinfo "10:20"') - self.assert_(u'ACK [2@0] {playlistinfo} Bad song index' in result) + self.sendRequest(u'playlistinfo "10:20"') + self.assertEqualResponse(u'ACK [2@0] {playlistinfo} Bad song index') def test_playlistinfo_with_too_high_end_of_range_returns_ok(self): - result = self.dispatcher.handle_request(u'playlistinfo "0:20"') - self.assert_(u'OK' in result) + self.sendRequest(u'playlistinfo "0:20"') + self.assertInResponse(u'OK') def test_playlistsearch(self): - result = self.dispatcher.handle_request( - u'playlistsearch "any" "needle"') - self.assert_(u'ACK [0@0] {} Not implemented' in result) + self.sendRequest( u'playlistsearch "any" "needle"') + self.assertEqualResponse(u'ACK [0@0] {} Not implemented') def test_playlistsearch_without_quotes(self): - result = self.dispatcher.handle_request(u'playlistsearch any "needle"') - self.assert_(u'ACK [0@0] {} Not implemented' in result) + self.sendRequest(u'playlistsearch any "needle"') + self.assertEqualResponse(u'ACK [0@0] {} Not implemented') def test_plchanges(self): self.backend.current_playlist.append( [Track(name='a'), Track(name='b'), Track(name='c')]) - result = self.dispatcher.handle_request(u'plchanges "0"') - self.assert_(u'Title: a' in result) - self.assert_(u'Title: b' in result) - self.assert_(u'Title: c' in result) - self.assert_(u'OK' in result) + + self.sendRequest(u'plchanges "0"') + self.assertInResponse(u'Title: a') + self.assertInResponse(u'Title: b') + self.assertInResponse(u'Title: c') + self.assertInResponse(u'OK') def test_plchanges_with_minus_one_returns_entire_playlist(self): self.backend.current_playlist.append( [Track(name='a'), Track(name='b'), Track(name='c')]) - result = self.dispatcher.handle_request(u'plchanges "-1"') - self.assert_(u'Title: a' in result) - self.assert_(u'Title: b' in result) - self.assert_(u'Title: c' in result) - self.assert_(u'OK' in result) + + self.sendRequest(u'plchanges "-1"') + self.assertInResponse(u'Title: a') + self.assertInResponse(u'Title: b') + self.assertInResponse(u'Title: c') + self.assertInResponse(u'OK') def test_plchanges_without_quotes_works(self): self.backend.current_playlist.append( [Track(name='a'), Track(name='b'), Track(name='c')]) - result = self.dispatcher.handle_request(u'plchanges 0') - self.assert_(u'Title: a' in result) - self.assert_(u'Title: b' in result) - self.assert_(u'Title: c' in result) - self.assert_(u'OK' in result) + + self.sendRequest(u'plchanges 0') + self.assertInResponse(u'Title: a') + self.assertInResponse(u'Title: b') + self.assertInResponse(u'Title: c') + self.assertInResponse(u'OK') def test_plchangesposid(self): self.backend.current_playlist.append([Track(), Track(), Track()]) - result = self.dispatcher.handle_request(u'plchangesposid "0"') + + self.sendRequest(u'plchangesposid "0"') cp_tracks = self.backend.current_playlist.cp_tracks.get() - self.assert_(u'cpos: 0' in result) - self.assert_(u'Id: %d' % cp_tracks[0][0] - in result) - self.assert_(u'cpos: 2' in result) - self.assert_(u'Id: %d' % cp_tracks[1][0] - in result) - self.assert_(u'cpos: 2' in result) - self.assert_(u'Id: %d' % cp_tracks[2][0] - in result) - self.assert_(u'OK' in result) + self.assertInResponse(u'cpos: 0') + self.assertInResponse(u'Id: %d' % cp_tracks[0][0]) + self.assertInResponse(u'cpos: 2') + self.assertInResponse(u'Id: %d' % cp_tracks[1][0]) + self.assertInResponse(u'cpos: 2') + self.assertInResponse(u'Id: %d' % cp_tracks[2][0]) + self.assertInResponse(u'OK') def test_shuffle_without_range(self): self.backend.current_playlist.append([ @@ -386,9 +392,10 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): Track(name='d'), Track(name='e'), Track(name='f'), ]) version = self.backend.current_playlist.version.get() - result = self.dispatcher.handle_request(u'shuffle') + + self.sendRequest(u'shuffle') self.assert_(version < self.backend.current_playlist.version.get()) - self.assert_(u'OK' in result) + self.assertInResponse(u'OK') def test_shuffle_with_open_range(self): self.backend.current_playlist.append([ @@ -396,14 +403,15 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): Track(name='d'), Track(name='e'), Track(name='f'), ]) version = self.backend.current_playlist.version.get() - result = self.dispatcher.handle_request(u'shuffle "4:"') + + self.sendRequest(u'shuffle "4:"') self.assert_(version < self.backend.current_playlist.version.get()) tracks = self.backend.current_playlist.tracks.get() self.assertEqual(tracks[0].name, 'a') self.assertEqual(tracks[1].name, 'b') self.assertEqual(tracks[2].name, 'c') self.assertEqual(tracks[3].name, 'd') - self.assert_(u'OK' in result) + self.assertInResponse(u'OK') def test_shuffle_with_closed_range(self): self.backend.current_playlist.append([ @@ -411,21 +419,23 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): Track(name='d'), Track(name='e'), Track(name='f'), ]) version = self.backend.current_playlist.version.get() - result = self.dispatcher.handle_request(u'shuffle "1:3"') + + self.sendRequest(u'shuffle "1:3"') self.assert_(version < self.backend.current_playlist.version.get()) tracks = self.backend.current_playlist.tracks.get() self.assertEqual(tracks[0].name, 'a') self.assertEqual(tracks[3].name, 'd') self.assertEqual(tracks[4].name, 'e') self.assertEqual(tracks[5].name, 'f') - self.assert_(u'OK' in result) + self.assertInResponse(u'OK') def test_swap(self): self.backend.current_playlist.append([ Track(name='a'), Track(name='b'), Track(name='c'), Track(name='d'), Track(name='e'), Track(name='f'), ]) - result = self.dispatcher.handle_request(u'swap "1" "4"') + + self.sendRequest(u'swap "1" "4"') tracks = self.backend.current_playlist.tracks.get() self.assertEqual(tracks[0].name, 'a') self.assertEqual(tracks[1].name, 'e') @@ -433,14 +443,15 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): self.assertEqual(tracks[3].name, 'd') self.assertEqual(tracks[4].name, 'b') self.assertEqual(tracks[5].name, 'f') - self.assert_(u'OK' in result) + self.assertInResponse(u'OK') def test_swapid(self): self.backend.current_playlist.append([ Track(name='a'), Track(name='b'), Track(name='c'), Track(name='d'), Track(name='e'), Track(name='f'), ]) - result = self.dispatcher.handle_request(u'swapid "1" "4"') + + self.sendRequest(u'swapid "1" "4"') tracks = self.backend.current_playlist.tracks.get() self.assertEqual(tracks[0].name, 'a') self.assertEqual(tracks[1].name, 'e') @@ -448,4 +459,4 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): self.assertEqual(tracks[3].name, 'd') self.assertEqual(tracks[4].name, 'b') self.assertEqual(tracks[5].name, 'f') - self.assert_(u'OK' in result) + self.assertInResponse(u'OK') From 93d2aa824074fd9b4c5be5fd11f2b795ae9fbb87 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Wed, 20 Jul 2011 17:53:24 +0200 Subject: [PATCH 15/43] Migrate music_db_test.py --- tests/frontends/mpd/music_db_test.py | 412 ------------------ tests/frontends/mpd/protocol/music_db_test.py | 343 +++++++++++++++ 2 files changed, 343 insertions(+), 412 deletions(-) delete mode 100644 tests/frontends/mpd/music_db_test.py create mode 100644 tests/frontends/mpd/protocol/music_db_test.py diff --git a/tests/frontends/mpd/music_db_test.py b/tests/frontends/mpd/music_db_test.py deleted file mode 100644 index 3793db9e..00000000 --- a/tests/frontends/mpd/music_db_test.py +++ /dev/null @@ -1,412 +0,0 @@ -import unittest - -from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpd.dispatcher import MpdDispatcher -from mopidy.mixers.dummy import DummyMixer - -class MusicDatabaseHandlerTest(unittest.TestCase): - def setUp(self): - self.backend = DummyBackend.start().proxy() - self.mixer = DummyMixer.start().proxy() - self.dispatcher = MpdDispatcher() - - def tearDown(self): - self.backend.stop().get() - self.mixer.stop().get() - - def test_count(self): - result = self.dispatcher.handle_request(u'count "tag" "needle"') - self.assert_(u'songs: 0' in result) - self.assert_(u'playtime: 0' in result) - self.assert_(u'OK' in result) - - def test_findadd(self): - result = self.dispatcher.handle_request(u'findadd "album" "what"') - self.assert_(u'OK' in result) - - def test_listall(self): - result = self.dispatcher.handle_request( - u'listall "file:///dev/urandom"') - self.assert_(u'ACK [0@0] {} Not implemented' in result) - - def test_listallinfo(self): - result = self.dispatcher.handle_request( - u'listallinfo "file:///dev/urandom"') - self.assert_(u'ACK [0@0] {} Not implemented' in result) - - def test_lsinfo_without_path_returns_same_as_listplaylists(self): - lsinfo_result = self.dispatcher.handle_request(u'lsinfo') - listplaylists_result = self.dispatcher.handle_request(u'listplaylists') - self.assertEqual(lsinfo_result, listplaylists_result) - - def test_lsinfo_with_empty_path_returns_same_as_listplaylists(self): - lsinfo_result = self.dispatcher.handle_request(u'lsinfo ""') - listplaylists_result = self.dispatcher.handle_request(u'listplaylists') - self.assertEqual(lsinfo_result, listplaylists_result) - - def test_lsinfo_for_root_returns_same_as_listplaylists(self): - lsinfo_result = self.dispatcher.handle_request(u'lsinfo "/"') - listplaylists_result = self.dispatcher.handle_request(u'listplaylists') - self.assertEqual(lsinfo_result, listplaylists_result) - - def test_update_without_uri(self): - result = self.dispatcher.handle_request(u'update') - self.assert_(u'OK' in result) - self.assert_(u'updating_db: 0' in result) - - def test_update_with_uri(self): - result = self.dispatcher.handle_request(u'update "file:///dev/urandom"') - self.assert_(u'OK' in result) - self.assert_(u'updating_db: 0' in result) - - def test_rescan_without_uri(self): - result = self.dispatcher.handle_request(u'rescan') - self.assert_(u'OK' in result) - self.assert_(u'updating_db: 0' in result) - - def test_rescan_with_uri(self): - result = self.dispatcher.handle_request(u'rescan "file:///dev/urandom"') - self.assert_(u'OK' in result) - self.assert_(u'updating_db: 0' in result) - - -class MusicDatabaseFindTest(unittest.TestCase): - def setUp(self): - self.backend = DummyBackend.start().proxy() - self.mixer = DummyMixer.start().proxy() - self.dispatcher = MpdDispatcher() - - def tearDown(self): - self.backend.stop().get() - self.mixer.stop().get() - - def test_find_album(self): - result = self.dispatcher.handle_request(u'find "album" "what"') - self.assert_(u'OK' in result) - - def test_find_album_without_quotes(self): - result = self.dispatcher.handle_request(u'find album "what"') - self.assert_(u'OK' in result) - - def test_find_artist(self): - result = self.dispatcher.handle_request(u'find "artist" "what"') - self.assert_(u'OK' in result) - - def test_find_artist_without_quotes(self): - result = self.dispatcher.handle_request(u'find artist "what"') - self.assert_(u'OK' in result) - - def test_find_title(self): - result = self.dispatcher.handle_request(u'find "title" "what"') - self.assert_(u'OK' in result) - - def test_find_title_without_quotes(self): - result = self.dispatcher.handle_request(u'find title "what"') - self.assert_(u'OK' in result) - - def test_find_date(self): - result = self.dispatcher.handle_request(u'find "date" "2002-01-01"') - self.assert_(u'OK' in result) - - def test_find_date_without_quotes(self): - result = self.dispatcher.handle_request(u'find date "2002-01-01"') - self.assert_(u'OK' in result) - - def test_find_date_with_capital_d_and_incomplete_date(self): - result = self.dispatcher.handle_request(u'find Date "2005"') - self.assert_(u'OK' in result) - - def test_find_else_should_fail(self): - - result = self.dispatcher.handle_request(u'find "somethingelse" "what"') - self.assertEqual(result[0], u'ACK [2@0] {find} incorrect arguments') - - def test_find_album_and_artist(self): - result = self.dispatcher.handle_request( - u'find album "album_what" artist "artist_what"') - self.assert_(u'OK' in result) - - -class MusicDatabaseListTest(unittest.TestCase): - def setUp(self): - self.backend = DummyBackend.start().proxy() - self.mixer = DummyMixer.start().proxy() - self.dispatcher = MpdDispatcher() - - def tearDown(self): - self.backend.stop().get() - self.mixer.stop().get() - - def test_list_foo_returns_ack(self): - result = self.dispatcher.handle_request(u'list "foo"') - self.assertEqual(result[0], - u'ACK [2@0] {list} incorrect arguments') - - ### Artist - - def test_list_artist_with_quotes(self): - result = self.dispatcher.handle_request(u'list "artist"') - self.assert_(u'OK' in result) - - def test_list_artist_without_quotes(self): - result = self.dispatcher.handle_request(u'list artist') - self.assert_(u'OK' in result) - - def test_list_artist_without_quotes_and_capitalized(self): - result = self.dispatcher.handle_request(u'list Artist') - self.assert_(u'OK' in result) - - def test_list_artist_with_query_of_one_token(self): - result = self.dispatcher.handle_request(u'list "artist" "anartist"') - self.assertEqual(result[0], - u'ACK [2@0] {list} should be "Album" for 3 arguments') - - def test_list_artist_with_unknown_field_in_query_returns_ack(self): - result = self.dispatcher.handle_request(u'list "artist" "foo" "bar"') - self.assertEqual(result[0], - u'ACK [2@0] {list} not able to parse args') - - def test_list_artist_by_artist(self): - result = self.dispatcher.handle_request( - u'list "artist" "artist" "anartist"') - self.assert_(u'OK' in result) - - def test_list_artist_by_album(self): - result = self.dispatcher.handle_request( - u'list "artist" "album" "analbum"') - self.assert_(u'OK' in result) - - def test_list_artist_by_full_date(self): - result = self.dispatcher.handle_request( - u'list "artist" "date" "2001-01-01"') - self.assert_(u'OK' in result) - - def test_list_artist_by_year(self): - result = self.dispatcher.handle_request( - u'list "artist" "date" "2001"') - self.assert_(u'OK' in result) - - def test_list_artist_by_genre(self): - result = self.dispatcher.handle_request( - u'list "artist" "genre" "agenre"') - self.assert_(u'OK' in result) - - def test_list_artist_by_artist_and_album(self): - result = self.dispatcher.handle_request( - u'list "artist" "artist" "anartist" "album" "analbum"') - self.assert_(u'OK' in result) - - ### Album - - def test_list_album_with_quotes(self): - result = self.dispatcher.handle_request(u'list "album"') - self.assert_(u'OK' in result) - - def test_list_album_without_quotes(self): - result = self.dispatcher.handle_request(u'list album') - self.assert_(u'OK' in result) - - def test_list_album_without_quotes_and_capitalized(self): - result = self.dispatcher.handle_request(u'list Album') - self.assert_(u'OK' in result) - - def test_list_album_with_artist_name(self): - result = self.dispatcher.handle_request(u'list "album" "anartist"') - self.assert_(u'OK' in result) - - def test_list_album_by_artist(self): - result = self.dispatcher.handle_request( - u'list "album" "artist" "anartist"') - self.assert_(u'OK' in result) - - def test_list_album_by_album(self): - result = self.dispatcher.handle_request( - u'list "album" "album" "analbum"') - self.assert_(u'OK' in result) - - def test_list_album_by_full_date(self): - result = self.dispatcher.handle_request( - u'list "album" "date" "2001-01-01"') - self.assert_(u'OK' in result) - - def test_list_album_by_year(self): - result = self.dispatcher.handle_request( - u'list "album" "date" "2001"') - self.assert_(u'OK' in result) - - def test_list_album_by_genre(self): - result = self.dispatcher.handle_request( - u'list "album" "genre" "agenre"') - self.assert_(u'OK' in result) - - def test_list_album_by_artist_and_album(self): - result = self.dispatcher.handle_request( - u'list "album" "artist" "anartist" "album" "analbum"') - self.assert_(u'OK' in result) - - ### Date - - def test_list_date_with_quotes(self): - result = self.dispatcher.handle_request(u'list "date"') - self.assert_(u'OK' in result) - - def test_list_date_without_quotes(self): - result = self.dispatcher.handle_request(u'list date') - self.assert_(u'OK' in result) - - def test_list_date_without_quotes_and_capitalized(self): - result = self.dispatcher.handle_request(u'list Date') - self.assert_(u'OK' in result) - - def test_list_date_with_query_of_one_token(self): - result = self.dispatcher.handle_request(u'list "date" "anartist"') - self.assertEqual(result[0], - u'ACK [2@0] {list} should be "Album" for 3 arguments') - - def test_list_date_by_artist(self): - result = self.dispatcher.handle_request( - u'list "date" "artist" "anartist"') - self.assert_(u'OK' in result) - - def test_list_date_by_album(self): - result = self.dispatcher.handle_request( - u'list "date" "album" "analbum"') - self.assert_(u'OK' in result) - - def test_list_date_by_full_date(self): - result = self.dispatcher.handle_request( - u'list "date" "date" "2001-01-01"') - self.assert_(u'OK' in result) - - def test_list_date_by_year(self): - result = self.dispatcher.handle_request(u'list "date" "date" "2001"') - self.assert_(u'OK' in result) - - def test_list_date_by_genre(self): - result = self.dispatcher.handle_request(u'list "date" "genre" "agenre"') - self.assert_(u'OK' in result) - - def test_list_date_by_artist_and_album(self): - result = self.dispatcher.handle_request( - u'list "date" "artist" "anartist" "album" "analbum"') - self.assert_(u'OK' in result) - - ### Genre - - def test_list_genre_with_quotes(self): - result = self.dispatcher.handle_request(u'list "genre"') - self.assert_(u'OK' in result) - - def test_list_genre_without_quotes(self): - result = self.dispatcher.handle_request(u'list genre') - self.assert_(u'OK' in result) - - def test_list_genre_without_quotes_and_capitalized(self): - result = self.dispatcher.handle_request(u'list Genre') - self.assert_(u'OK' in result) - - def test_list_genre_with_query_of_one_token(self): - result = self.dispatcher.handle_request(u'list "genre" "anartist"') - self.assertEqual(result[0], - u'ACK [2@0] {list} should be "Album" for 3 arguments') - - def test_list_genre_by_artist(self): - result = self.dispatcher.handle_request( - u'list "genre" "artist" "anartist"') - self.assert_(u'OK' in result) - - def test_list_genre_by_album(self): - result = self.dispatcher.handle_request( - u'list "genre" "album" "analbum"') - self.assert_(u'OK' in result) - - def test_list_genre_by_full_date(self): - result = self.dispatcher.handle_request( - u'list "genre" "date" "2001-01-01"') - self.assert_(u'OK' in result) - - def test_list_genre_by_year(self): - result = self.dispatcher.handle_request( - u'list "genre" "date" "2001"') - self.assert_(u'OK' in result) - - def test_list_genre_by_genre(self): - result = self.dispatcher.handle_request( - u'list "genre" "genre" "agenre"') - self.assert_(u'OK' in result) - - def test_list_genre_by_artist_and_album(self): - result = self.dispatcher.handle_request( - u'list "genre" "artist" "anartist" "album" "analbum"') - self.assert_(u'OK' in result) - - -class MusicDatabaseSearchTest(unittest.TestCase): - def setUp(self): - self.backend = DummyBackend.start().proxy() - self.mixer = DummyMixer.start().proxy() - self.dispatcher = MpdDispatcher() - - def tearDown(self): - self.backend.stop().get() - self.mixer.stop().get() - - def test_search_album(self): - result = self.dispatcher.handle_request(u'search "album" "analbum"') - self.assert_(u'OK' in result) - - def test_search_album_without_quotes(self): - result = self.dispatcher.handle_request(u'search album "analbum"') - self.assert_(u'OK' in result) - - def test_search_artist(self): - result = self.dispatcher.handle_request(u'search "artist" "anartist"') - self.assert_(u'OK' in result) - - def test_search_artist_without_quotes(self): - result = self.dispatcher.handle_request(u'search artist "anartist"') - self.assert_(u'OK' in result) - - def test_search_filename(self): - result = self.dispatcher.handle_request( - u'search "filename" "afilename"') - self.assert_(u'OK' in result) - - def test_search_filename_without_quotes(self): - result = self.dispatcher.handle_request(u'search filename "afilename"') - self.assert_(u'OK' in result) - - def test_search_title(self): - result = self.dispatcher.handle_request(u'search "title" "atitle"') - self.assert_(u'OK' in result) - - def test_search_title_without_quotes(self): - result = self.dispatcher.handle_request(u'search title "atitle"') - self.assert_(u'OK' in result) - - def test_search_any(self): - result = self.dispatcher.handle_request(u'search "any" "anything"') - self.assert_(u'OK' in result) - - def test_search_any_without_quotes(self): - result = self.dispatcher.handle_request(u'search any "anything"') - self.assert_(u'OK' in result) - - def test_search_date(self): - result = self.dispatcher.handle_request(u'search "date" "2002-01-01"') - self.assert_(u'OK' in result) - - def test_search_date_without_quotes(self): - result = self.dispatcher.handle_request(u'search date "2002-01-01"') - self.assert_(u'OK' in result) - - def test_search_date_with_capital_d_and_incomplete_date(self): - result = self.dispatcher.handle_request(u'search Date "2005"') - self.assert_(u'OK' in result) - - def test_search_else_should_fail(self): - result = self.dispatcher.handle_request( - u'search "sometype" "something"') - self.assertEqual(result[0], u'ACK [2@0] {search} incorrect arguments') - - diff --git a/tests/frontends/mpd/protocol/music_db_test.py b/tests/frontends/mpd/protocol/music_db_test.py new file mode 100644 index 00000000..dc0789c9 --- /dev/null +++ b/tests/frontends/mpd/protocol/music_db_test.py @@ -0,0 +1,343 @@ +from tests.frontends.mpd import protocol + +class MusicDatabaseHandlerTest(protocol.BaseTestCase): + def test_count(self): + self.sendRequest(u'count "tag" "needle"') + self.assertInResponse(u'songs: 0') + self.assertInResponse(u'playtime: 0') + self.assertInResponse(u'OK') + + def test_findadd(self): + self.sendRequest(u'findadd "album" "what"') + self.assertInResponse(u'OK') + + def test_listall(self): + self.sendRequest(u'listall "file:///dev/urandom"') + self.assertEqualResponse(u'ACK [0@0] {} Not implemented') + + def test_listallinfo(self): + self.sendRequest(u'listallinfo "file:///dev/urandom"') + self.assertEqualResponse(u'ACK [0@0] {} Not implemented') + + def test_lsinfo_without_path_returns_same_as_listplaylists(self): + lsinfo_response = self.sendRequest(u'lsinfo') + listplaylists_response = self.sendRequest(u'listplaylists') + self.assertEqual(lsinfo_response, listplaylists_response) + + def test_lsinfo_with_empty_path_returns_same_as_listplaylists(self): + lsinfo_response = self.sendRequest(u'lsinfo ""') + listplaylists_response = self.sendRequest(u'listplaylists') + self.assertEqual(lsinfo_response, listplaylists_response) + + def test_lsinfo_for_root_returns_same_as_listplaylists(self): + lsinfo_response = self.sendRequest(u'lsinfo "/"') + listplaylists_response = self.sendRequest(u'listplaylists') + self.assertEqual(lsinfo_response, listplaylists_response) + + def test_update_without_uri(self): + self.sendRequest(u'update') + self.assertInResponse(u'updating_db: 0') + self.assertInResponse(u'OK') + + def test_update_with_uri(self): + self.sendRequest(u'update "file:///dev/urandom"') + self.assertInResponse(u'updating_db: 0') + self.assertInResponse(u'OK') + + def test_rescan_without_uri(self): + self.sendRequest(u'rescan') + self.assertInResponse(u'updating_db: 0') + self.assertInResponse(u'OK') + + def test_rescan_with_uri(self): + self.sendRequest(u'rescan "file:///dev/urandom"') + self.assertInResponse(u'updating_db: 0') + self.assertInResponse(u'OK') + + +class MusicDatabaseFindTest(protocol.BaseTestCase): + def test_find_album(self): + self.sendRequest(u'find "album" "what"') + self.assertInResponse(u'OK') + + def test_find_album_without_quotes(self): + self.sendRequest(u'find album "what"') + self.assertInResponse(u'OK') + + def test_find_artist(self): + self.sendRequest(u'find "artist" "what"') + self.assertInResponse(u'OK') + + def test_find_artist_without_quotes(self): + self.sendRequest(u'find artist "what"') + self.assertInResponse(u'OK') + + def test_find_title(self): + self.sendRequest(u'find "title" "what"') + self.assertInResponse(u'OK') + + def test_find_title_without_quotes(self): + self.sendRequest(u'find title "what"') + self.assertInResponse(u'OK') + + def test_find_date(self): + self.sendRequest(u'find "date" "2002-01-01"') + self.assertInResponse(u'OK') + + def test_find_date_without_quotes(self): + self.sendRequest(u'find date "2002-01-01"') + self.assertInResponse(u'OK') + + def test_find_date_with_capital_d_and_incomplete_date(self): + self.sendRequest(u'find Date "2005"') + self.assertInResponse(u'OK') + + def test_find_else_should_fail(self): + self.sendRequest(u'find "somethingelse" "what"') + self.assertEqualResponse(u'ACK [2@0] {find} incorrect arguments') + + def test_find_album_and_artist(self): + self.sendRequest(u'find album "album_what" artist "artist_what"') + self.assertInResponse(u'OK') + + +class MusicDatabaseListTest(protocol.BaseTestCase): + def test_list_foo_returns_ack(self): + self.sendRequest(u'list "foo"') + self.assertEqualResponse(u'ACK [2@0] {list} incorrect arguments') + + ### Artist + + def test_list_artist_with_quotes(self): + self.sendRequest(u'list "artist"') + self.assertInResponse(u'OK') + + def test_list_artist_without_quotes(self): + self.sendRequest(u'list artist') + self.assertInResponse(u'OK') + + def test_list_artist_without_quotes_and_capitalized(self): + self.sendRequest(u'list Artist') + self.assertInResponse(u'OK') + + def test_list_artist_with_query_of_one_token(self): + self.sendRequest(u'list "artist" "anartist"') + self.assertEqualResponse( + u'ACK [2@0] {list} should be "Album" for 3 arguments') + + def test_list_artist_with_unknown_field_in_query_returns_ack(self): + self.sendRequest(u'list "artist" "foo" "bar"') + self.assertEqualResponse(u'ACK [2@0] {list} not able to parse args') + + def test_list_artist_by_artist(self): + self.sendRequest(u'list "artist" "artist" "anartist"') + self.assertInResponse(u'OK') + + def test_list_artist_by_album(self): + self.sendRequest(u'list "artist" "album" "analbum"') + self.assertInResponse(u'OK') + + def test_list_artist_by_full_date(self): + self.sendRequest(u'list "artist" "date" "2001-01-01"') + self.assertInResponse(u'OK') + + def test_list_artist_by_year(self): + self.sendRequest(u'list "artist" "date" "2001"') + self.assertInResponse(u'OK') + + def test_list_artist_by_genre(self): + self.sendRequest(u'list "artist" "genre" "agenre"') + self.assertInResponse(u'OK') + + def test_list_artist_by_artist_and_album(self): + self.sendRequest( + u'list "artist" "artist" "anartist" "album" "analbum"') + self.assertInResponse(u'OK') + + ### Album + + def test_list_album_with_quotes(self): + self.sendRequest(u'list "album"') + self.assertInResponse(u'OK') + + def test_list_album_without_quotes(self): + self.sendRequest(u'list album') + self.assertInResponse(u'OK') + + def test_list_album_without_quotes_and_capitalized(self): + self.sendRequest(u'list Album') + self.assertInResponse(u'OK') + + def test_list_album_with_artist_name(self): + self.sendRequest(u'list "album" "anartist"') + self.assertInResponse(u'OK') + + def test_list_album_by_artist(self): + self.sendRequest(u'list "album" "artist" "anartist"') + self.assertInResponse(u'OK') + + def test_list_album_by_album(self): + self.sendRequest(u'list "album" "album" "analbum"') + self.assertInResponse(u'OK') + + def test_list_album_by_full_date(self): + self.sendRequest(u'list "album" "date" "2001-01-01"') + self.assertInResponse(u'OK') + + def test_list_album_by_year(self): + self.sendRequest(u'list "album" "date" "2001"') + self.assertInResponse(u'OK') + + def test_list_album_by_genre(self): + self.sendRequest(u'list "album" "genre" "agenre"') + self.assertInResponse(u'OK') + + def test_list_album_by_artist_and_album(self): + self.sendRequest( + u'list "album" "artist" "anartist" "album" "analbum"') + self.assertInResponse(u'OK') + + ### Date + + def test_list_date_with_quotes(self): + self.sendRequest(u'list "date"') + self.assertInResponse(u'OK') + + def test_list_date_without_quotes(self): + self.sendRequest(u'list date') + self.assertInResponse(u'OK') + + def test_list_date_without_quotes_and_capitalized(self): + self.sendRequest(u'list Date') + self.assertInResponse(u'OK') + + def test_list_date_with_query_of_one_token(self): + self.sendRequest(u'list "date" "anartist"') + self.assertEqualResponse( + u'ACK [2@0] {list} should be "Album" for 3 arguments') + + def test_list_date_by_artist(self): + self.sendRequest(u'list "date" "artist" "anartist"') + self.assertInResponse(u'OK') + + def test_list_date_by_album(self): + self.sendRequest(u'list "date" "album" "analbum"') + self.assertInResponse(u'OK') + + def test_list_date_by_full_date(self): + self.sendRequest(u'list "date" "date" "2001-01-01"') + self.assertInResponse(u'OK') + + def test_list_date_by_year(self): + self.sendRequest(u'list "date" "date" "2001"') + self.assertInResponse(u'OK') + + def test_list_date_by_genre(self): + self.sendRequest(u'list "date" "genre" "agenre"') + self.assertInResponse(u'OK') + + def test_list_date_by_artist_and_album(self): + self.sendRequest(u'list "date" "artist" "anartist" "album" "analbum"') + self.assertInResponse(u'OK') + + ### Genre + + def test_list_genre_with_quotes(self): + self.sendRequest(u'list "genre"') + self.assertInResponse(u'OK') + + def test_list_genre_without_quotes(self): + self.sendRequest(u'list genre') + self.assertInResponse(u'OK') + + def test_list_genre_without_quotes_and_capitalized(self): + self.sendRequest(u'list Genre') + self.assertInResponse(u'OK') + + def test_list_genre_with_query_of_one_token(self): + self.sendRequest(u'list "genre" "anartist"') + self.assertEqualResponse( + u'ACK [2@0] {list} should be "Album" for 3 arguments') + + def test_list_genre_by_artist(self): + self.sendRequest(u'list "genre" "artist" "anartist"') + self.assertInResponse(u'OK') + + def test_list_genre_by_album(self): + self.sendRequest(u'list "genre" "album" "analbum"') + self.assertInResponse(u'OK') + + def test_list_genre_by_full_date(self): + self.sendRequest(u'list "genre" "date" "2001-01-01"') + self.assertInResponse(u'OK') + + def test_list_genre_by_year(self): + self.sendRequest(u'list "genre" "date" "2001"') + self.assertInResponse(u'OK') + + def test_list_genre_by_genre(self): + self.sendRequest(u'list "genre" "genre" "agenre"') + self.assertInResponse(u'OK') + + def test_list_genre_by_artist_and_album(self): + self.sendRequest( + u'list "genre" "artist" "anartist" "album" "analbum"') + self.assertInResponse(u'OK') + + +class MusicDatabaseSearchTest(protocol.BaseTestCase): + def test_search_album(self): + self.sendRequest(u'search "album" "analbum"') + self.assertInResponse(u'OK') + + def test_search_album_without_quotes(self): + self.sendRequest(u'search album "analbum"') + self.assertInResponse(u'OK') + + def test_search_artist(self): + self.sendRequest(u'search "artist" "anartist"') + self.assertInResponse(u'OK') + + def test_search_artist_without_quotes(self): + self.sendRequest(u'search artist "anartist"') + self.assertInResponse(u'OK') + + def test_search_filename(self): + self.sendRequest(u'search "filename" "afilename"') + self.assertInResponse(u'OK') + + def test_search_filename_without_quotes(self): + self.sendRequest(u'search filename "afilename"') + self.assertInResponse(u'OK') + + def test_search_title(self): + self.sendRequest(u'search "title" "atitle"') + self.assertInResponse(u'OK') + + def test_search_title_without_quotes(self): + self.sendRequest(u'search title "atitle"') + self.assertInResponse(u'OK') + + def test_search_any(self): + self.sendRequest(u'search "any" "anything"') + self.assertInResponse(u'OK') + + def test_search_any_without_quotes(self): + self.sendRequest(u'search any "anything"') + self.assertInResponse(u'OK') + + def test_search_date(self): + self.sendRequest(u'search "date" "2002-01-01"') + self.assertInResponse(u'OK') + + def test_search_date_without_quotes(self): + self.sendRequest(u'search date "2002-01-01"') + self.assertInResponse(u'OK') + + def test_search_date_with_capital_d_and_incomplete_date(self): + self.sendRequest(u'search Date "2005"') + self.assertInResponse(u'OK') + + def test_search_else_should_fail(self): + self.sendRequest(u'search "sometype" "something"') + self.assertEqualResponse(u'ACK [2@0] {search} incorrect arguments') From c3a498e62bf138676a6d9c881ea5ff6bf92a06c3 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Wed, 20 Jul 2011 18:00:59 +0200 Subject: [PATCH 16/43] Migrate stickers_test --- tests/frontends/mpd/protocol/stickers_test.py | 32 +++++++++++++ tests/frontends/mpd/stickers_test.py | 45 ------------------- 2 files changed, 32 insertions(+), 45 deletions(-) create mode 100644 tests/frontends/mpd/protocol/stickers_test.py delete mode 100644 tests/frontends/mpd/stickers_test.py diff --git a/tests/frontends/mpd/protocol/stickers_test.py b/tests/frontends/mpd/protocol/stickers_test.py new file mode 100644 index 00000000..1c48cfd3 --- /dev/null +++ b/tests/frontends/mpd/protocol/stickers_test.py @@ -0,0 +1,32 @@ +from tests.frontends.mpd import protocol + +class StickersHandlerTest(protocol.BaseTestCase): + def test_sticker_get(self): + self.sendRequest( + u'sticker get "song" "file:///dev/urandom" "a_name"') + self.assertEqualResponse(u'ACK [0@0] {} Not implemented') + + def test_sticker_set(self): + self.sendRequest( + u'sticker set "song" "file:///dev/urandom" "a_name" "a_value"') + self.assertEqualResponse(u'ACK [0@0] {} Not implemented') + + def test_sticker_delete_with_name(self): + self.sendRequest( + u'sticker delete "song" "file:///dev/urandom" "a_name"') + self.assertEqualResponse(u'ACK [0@0] {} Not implemented') + + def test_sticker_delete_without_name(self): + self.sendRequest( + u'sticker delete "song" "file:///dev/urandom"') + self.assertEqualResponse(u'ACK [0@0] {} Not implemented') + + def test_sticker_list(self): + self.sendRequest( + u'sticker list "song" "file:///dev/urandom"') + self.assertEqualResponse(u'ACK [0@0] {} Not implemented') + + def test_sticker_find(self): + self.sendRequest( + u'sticker find "song" "file:///dev/urandom" "a_name"') + self.assertEqualResponse(u'ACK [0@0] {} Not implemented') diff --git a/tests/frontends/mpd/stickers_test.py b/tests/frontends/mpd/stickers_test.py deleted file mode 100644 index 86ac8aec..00000000 --- a/tests/frontends/mpd/stickers_test.py +++ /dev/null @@ -1,45 +0,0 @@ -import unittest - -from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpd.dispatcher import MpdDispatcher -from mopidy.mixers.dummy import DummyMixer - -class StickersHandlerTest(unittest.TestCase): - def setUp(self): - self.backend = DummyBackend.start().proxy() - self.mixer = DummyMixer.start().proxy() - self.dispatcher = MpdDispatcher() - - def tearDown(self): - self.backend.stop().get() - self.mixer.stop().get() - - def test_sticker_get(self): - result = self.dispatcher.handle_request( - u'sticker get "song" "file:///dev/urandom" "a_name"') - self.assert_(u'ACK [0@0] {} Not implemented' in result) - - def test_sticker_set(self): - result = self.dispatcher.handle_request( - u'sticker set "song" "file:///dev/urandom" "a_name" "a_value"') - self.assert_(u'ACK [0@0] {} Not implemented' in result) - - def test_sticker_delete_with_name(self): - result = self.dispatcher.handle_request( - u'sticker delete "song" "file:///dev/urandom" "a_name"') - self.assert_(u'ACK [0@0] {} Not implemented' in result) - - def test_sticker_delete_without_name(self): - result = self.dispatcher.handle_request( - u'sticker delete "song" "file:///dev/urandom"') - self.assert_(u'ACK [0@0] {} Not implemented' in result) - - def test_sticker_list(self): - result = self.dispatcher.handle_request( - u'sticker list "song" "file:///dev/urandom"') - self.assert_(u'ACK [0@0] {} Not implemented' in result) - - def test_sticker_find(self): - result = self.dispatcher.handle_request( - u'sticker find "song" "file:///dev/urandom" "a_name"') - self.assert_(u'ACK [0@0] {} Not implemented' in result) From ec69ce204474ebd4fe9e26845b9c0ba36820d33e Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Wed, 20 Jul 2011 19:36:03 +0200 Subject: [PATCH 17/43] Partially migrate status_test --- tests/frontends/mpd/protocol/status_test.py | 51 ++++++++++++++++ tests/frontends/mpd/status_test.py | 66 ++++----------------- 2 files changed, 63 insertions(+), 54 deletions(-) create mode 100644 tests/frontends/mpd/protocol/status_test.py diff --git a/tests/frontends/mpd/protocol/status_test.py b/tests/frontends/mpd/protocol/status_test.py new file mode 100644 index 00000000..6762a4fb --- /dev/null +++ b/tests/frontends/mpd/protocol/status_test.py @@ -0,0 +1,51 @@ +from mopidy.models import Track + +from tests.frontends.mpd import protocol + +class StatusHandlerTest(protocol.BaseTestCase): + def test_clearerror(self): + self.sendRequest(u'clearerror') + self.assertEqualResponse(u'ACK [0@0] {} Not implemented') + + def test_currentsong(self): + track = Track() + self.backend.current_playlist.append([track]) + self.backend.playback.play() + self.sendRequest(u'currentsong') + self.assertInResponse(u'file: ') + self.assertInResponse(u'Time: 0') + self.assertInResponse(u'Artist: ') + self.assertInResponse(u'Title: ') + self.assertInResponse(u'Album: ') + self.assertInResponse(u'Track: 0') + self.assertInResponse(u'Date: ') + self.assertInResponse(u'Pos: 0') + self.assertInResponse(u'Id: 0') + self.assertInResponse(u'OK') + + def test_currentsong_without_song(self): + self.sendRequest(u'currentsong') + self.assertInResponse(u'OK') + + def test_idle_without_subsystems(self): + # FIXME this is not the correct behaviour for idle... + self.sendRequest(u'idle') + self.assertInResponse(u'OK') + + def test_idle_with_subsystems(self): + # FIXME this is not the correct behaviour for idle... + self.sendRequest(u'idle database playlist') + self.assertInResponse(u'OK') + + def test_noidle(self): + # FIXME this is not the correct behaviour for idle... + self.sendRequest(u'noidle') + self.assertInResponse(u'OK') + + def test_stats_command(self): + self.sendRequest(u'stats') + self.assertInResponse(u'OK') + + def test_status_command(self): + self.sendRequest(u'status') + self.assertInResponse(u'OK') diff --git a/tests/frontends/mpd/status_test.py b/tests/frontends/mpd/status_test.py index a7ed921f..d277227a 100644 --- a/tests/frontends/mpd/status_test.py +++ b/tests/frontends/mpd/status_test.py @@ -1,67 +1,29 @@ import unittest -from mopidy.backends.base import PlaybackController -from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpd.dispatcher import MpdDispatcher +from mopidy.backends import dummy as backend +from mopidy.frontends.mpd import dispatcher from mopidy.frontends.mpd.protocol import status -from mopidy.mixers.dummy import DummyMixer +from mopidy.mixers import dummy as mixer from mopidy.models import Track -PAUSED = PlaybackController.PAUSED -PLAYING = PlaybackController.PLAYING -STOPPED = PlaybackController.STOPPED +PAUSED = backend.PlaybackController.PAUSED +PLAYING = backend.PlaybackController.PLAYING +STOPPED = backend.PlaybackController.STOPPED + +# FIXME migrate to using protocol.BaseTestCase instead of status.stats +# directly? class StatusHandlerTest(unittest.TestCase): def setUp(self): - self.backend = DummyBackend.start().proxy() - self.mixer = DummyMixer.start().proxy() - self.dispatcher = MpdDispatcher() + self.backend = backend.DummyBackend.start().proxy() + self.mixer = mixer.DummyMixer.start().proxy() + self.dispatcher = dispatcher.MpdDispatcher() self.context = self.dispatcher.context def tearDown(self): self.backend.stop().get() self.mixer.stop().get() - def test_clearerror(self): - result = self.dispatcher.handle_request(u'clearerror') - self.assert_(u'ACK [0@0] {} Not implemented' in result) - - def test_currentsong(self): - track = Track() - self.backend.current_playlist.append([track]) - self.backend.playback.play() - result = self.dispatcher.handle_request(u'currentsong') - self.assert_(u'file: ' in result) - self.assert_(u'Time: 0' in result) - self.assert_(u'Artist: ' in result) - self.assert_(u'Title: ' in result) - self.assert_(u'Album: ' in result) - self.assert_(u'Track: 0' in result) - self.assert_(u'Date: ' in result) - self.assert_(u'Pos: 0' in result) - self.assert_(u'Id: 0' in result) - self.assert_(u'OK' in result) - - def test_currentsong_without_song(self): - result = self.dispatcher.handle_request(u'currentsong') - self.assert_(u'OK' in result) - - def test_idle_without_subsystems(self): - result = self.dispatcher.handle_request(u'idle') - self.assert_(u'OK' in result) - - def test_idle_with_subsystems(self): - result = self.dispatcher.handle_request(u'idle database playlist') - self.assert_(u'OK' in result) - - def test_noidle(self): - result = self.dispatcher.handle_request(u'noidle') - self.assert_(u'OK' in result) - - def test_stats_command(self): - result = self.dispatcher.handle_request(u'stats') - self.assert_(u'OK' in result) - def test_stats_method(self): result = status.stats(self.context) self.assert_('artists' in result) @@ -79,10 +41,6 @@ class StatusHandlerTest(unittest.TestCase): self.assert_('playtime' in result) self.assert_(int(result['playtime']) >= 0) - def test_status_command(self): - result = self.dispatcher.handle_request(u'status') - self.assert_(u'OK' in result) - def test_status_method_contains_volume_which_defaults_to_0(self): result = dict(status.status(self.context)) self.assert_('volume' in result) From 5d18c64bf991f8fa568a7c8daaeb6f36fadc13c8 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Wed, 20 Jul 2011 19:44:06 +0200 Subject: [PATCH 18/43] Migrate stored_playlists_test --- .../mpd/protocol/stored_playlists_test.py | 93 ++++++++++++++++ tests/frontends/mpd/stored_playlists_test.py | 102 ------------------ 2 files changed, 93 insertions(+), 102 deletions(-) create mode 100644 tests/frontends/mpd/protocol/stored_playlists_test.py delete mode 100644 tests/frontends/mpd/stored_playlists_test.py diff --git a/tests/frontends/mpd/protocol/stored_playlists_test.py b/tests/frontends/mpd/protocol/stored_playlists_test.py new file mode 100644 index 00000000..6d9448a6 --- /dev/null +++ b/tests/frontends/mpd/protocol/stored_playlists_test.py @@ -0,0 +1,93 @@ +import datetime as dt + +from mopidy.models import Track, Playlist + +from tests.frontends.mpd import protocol + +class StoredPlaylistsHandlerTest(protocol.BaseTestCase): + def test_listplaylist(self): + self.backend.stored_playlists.playlists = [ + Playlist(name='name', tracks=[Track(uri='file:///dev/urandom')])] + + self.sendRequest(u'listplaylist "name"') + self.assertInResponse(u'file: file:///dev/urandom') + self.assertInResponse(u'OK') + + def test_listplaylist_fails_if_no_playlist_is_found(self): + self.sendRequest(u'listplaylist "name"') + self.assertEqualResponse(u'ACK [50@0] {listplaylist} No such playlist') + + def test_listplaylistinfo(self): + self.backend.stored_playlists.playlists = [ + Playlist(name='name', tracks=[Track(uri='file:///dev/urandom')])] + + self.sendRequest(u'listplaylistinfo "name"') + self.assertInResponse(u'file: file:///dev/urandom') + self.assertInResponse(u'Track: 0') + self.assertNotInResponse(u'Pos: 0') + self.assertInResponse(u'OK') + + def test_listplaylistinfo_fails_if_no_playlist_is_found(self): + self.sendRequest(u'listplaylistinfo "name"') + self.assertEqualResponse( + u'ACK [50@0] {listplaylistinfo} No such playlist') + + def test_listplaylists(self): + last_modified = dt.datetime(2001, 3, 17, 13, 41, 17, 12345) + self.backend.stored_playlists.playlists = [Playlist(name='a', + last_modified=last_modified)] + + self.sendRequest(u'listplaylists') + self.assertInResponse(u'playlist: a') + # Date without microseconds and with time zone information + self.assertInResponse(u'Last-Modified: 2001-03-17T13:41:17Z') + self.assertInResponse(u'OK') + + def test_load_known_playlist_appends_to_current_playlist(self): + self.backend.current_playlist.append([Track(uri='a'), Track(uri='b')]) + self.assertEqual(len(self.backend.current_playlist.tracks.get()), 2) + self.backend.stored_playlists.playlists = [Playlist(name='A-list', + tracks=[Track(uri='c'), Track(uri='d'), Track(uri='e')])] + + self.sendRequest(u'load "A-list"') + tracks = self.backend.current_playlist.tracks.get() + self.assertEqual(5, len(tracks)) + self.assertEqual('a', tracks[0].uri) + self.assertEqual('b', tracks[1].uri) + self.assertEqual('c', tracks[2].uri) + self.assertEqual('d', tracks[3].uri) + self.assertEqual('e', tracks[4].uri) + self.assertInResponse(u'OK') + + def test_load_unknown_playlist_acks(self): + self.sendRequest(u'load "unknown playlist"') + self.assertEqual(0, len(self.backend.current_playlist.tracks.get())) + self.assertEqualResponse(u'ACK [50@0] {load} No such playlist') + + def test_playlistadd(self): + self.sendRequest(u'playlistadd "name" "file:///dev/urandom"') + self.assertEqualResponse(u'ACK [0@0] {} Not implemented') + + def test_playlistclear(self): + self.sendRequest(u'playlistclear "name"') + self.assertEqualResponse(u'ACK [0@0] {} Not implemented') + + def test_playlistdelete(self): + self.sendRequest(u'playlistdelete "name" "5"') + self.assertEqualResponse(u'ACK [0@0] {} Not implemented') + + def test_playlistmove(self): + self.sendRequest(u'playlistmove "name" "5" "10"') + self.assertEqualResponse(u'ACK [0@0] {} Not implemented') + + def test_rename(self): + self.sendRequest(u'rename "old_name" "new_name"') + self.assertEqualResponse(u'ACK [0@0] {} Not implemented') + + def test_rm(self): + self.sendRequest(u'rm "name"') + self.assertEqualResponse(u'ACK [0@0] {} Not implemented') + + def test_save(self): + self.sendRequest(u'save "name"') + self.assertEqualResponse(u'ACK [0@0] {} Not implemented') diff --git a/tests/frontends/mpd/stored_playlists_test.py b/tests/frontends/mpd/stored_playlists_test.py deleted file mode 100644 index 04bab6f1..00000000 --- a/tests/frontends/mpd/stored_playlists_test.py +++ /dev/null @@ -1,102 +0,0 @@ -import datetime as dt -import unittest - -from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpd.dispatcher import MpdDispatcher -from mopidy.mixers.dummy import DummyMixer -from mopidy.models import Track, Playlist - -class StoredPlaylistsHandlerTest(unittest.TestCase): - def setUp(self): - self.backend = DummyBackend.start().proxy() - self.mixer = DummyMixer.start().proxy() - self.dispatcher = MpdDispatcher() - - def tearDown(self): - self.backend.stop().get() - self.mixer.stop().get() - - def test_listplaylist(self): - self.backend.stored_playlists.playlists = [ - Playlist(name='name', tracks=[Track(uri='file:///dev/urandom')])] - result = self.dispatcher.handle_request(u'listplaylist "name"') - self.assert_(u'file: file:///dev/urandom' in result) - self.assert_(u'OK' in result) - - def test_listplaylist_fails_if_no_playlist_is_found(self): - result = self.dispatcher.handle_request(u'listplaylist "name"') - self.assertEqual(result[0], - u'ACK [50@0] {listplaylist} No such playlist') - - def test_listplaylistinfo(self): - self.backend.stored_playlists.playlists = [ - Playlist(name='name', tracks=[Track(uri='file:///dev/urandom')])] - result = self.dispatcher.handle_request(u'listplaylistinfo "name"') - self.assert_(u'file: file:///dev/urandom' in result) - self.assert_(u'Track: 0' in result) - self.assert_(u'Pos: 0' not in result) - self.assert_(u'OK' in result) - - def test_listplaylistinfo_fails_if_no_playlist_is_found(self): - result = self.dispatcher.handle_request(u'listplaylistinfo "name"') - self.assertEqual(result[0], - u'ACK [50@0] {listplaylistinfo} No such playlist') - - def test_listplaylists(self): - last_modified = dt.datetime(2001, 3, 17, 13, 41, 17, 12345) - self.backend.stored_playlists.playlists = [Playlist(name='a', - last_modified=last_modified)] - result = self.dispatcher.handle_request(u'listplaylists') - self.assert_(u'playlist: a' in result) - # Date without microseconds and with time zone information - self.assert_(u'Last-Modified: 2001-03-17T13:41:17Z' in result) - self.assert_(u'OK' in result) - - def test_load_known_playlist_appends_to_current_playlist(self): - self.backend.current_playlist.append([Track(uri='a'), Track(uri='b')]) - self.assertEqual(len(self.backend.current_playlist.tracks.get()), 2) - self.backend.stored_playlists.playlists = [Playlist(name='A-list', - tracks=[Track(uri='c'), Track(uri='d'), Track(uri='e')])] - result = self.dispatcher.handle_request(u'load "A-list"') - self.assert_(u'OK' in result) - tracks = self.backend.current_playlist.tracks.get() - self.assertEqual(len(tracks), 5) - self.assertEqual(tracks[0].uri, 'a') - self.assertEqual(tracks[1].uri, 'b') - self.assertEqual(tracks[2].uri, 'c') - self.assertEqual(tracks[3].uri, 'd') - self.assertEqual(tracks[4].uri, 'e') - - def test_load_unknown_playlist_acks(self): - result = self.dispatcher.handle_request(u'load "unknown playlist"') - self.assert_(u'ACK [50@0] {load} No such playlist' in result) - self.assertEqual(len(self.backend.current_playlist.tracks.get()), 0) - - def test_playlistadd(self): - result = self.dispatcher.handle_request( - u'playlistadd "name" "file:///dev/urandom"') - self.assert_(u'ACK [0@0] {} Not implemented' in result) - - def test_playlistclear(self): - result = self.dispatcher.handle_request(u'playlistclear "name"') - self.assert_(u'ACK [0@0] {} Not implemented' in result) - - def test_playlistdelete(self): - result = self.dispatcher.handle_request(u'playlistdelete "name" "5"') - self.assert_(u'ACK [0@0] {} Not implemented' in result) - - def test_playlistmove(self): - result = self.dispatcher.handle_request(u'playlistmove "name" "5" "10"') - self.assert_(u'ACK [0@0] {} Not implemented' in result) - - def test_rename(self): - result = self.dispatcher.handle_request(u'rename "old_name" "new_name"') - self.assert_(u'ACK [0@0] {} Not implemented' in result) - - def test_rm(self): - result = self.dispatcher.handle_request(u'rm "name"') - self.assert_(u'ACK [0@0] {} Not implemented' in result) - - def test_save(self): - result = self.dispatcher.handle_request(u'save "name"') - self.assert_(u'ACK [0@0] {} Not implemented' in result) From 730368dbeb2d2db3958681adae6882b9911cc428 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Wed, 20 Jul 2011 20:00:25 +0200 Subject: [PATCH 19/43] Migrate regression_test --- .../mpd/{ => protocol}/regression_test.py | 120 ++++++------------ 1 file changed, 42 insertions(+), 78 deletions(-) rename tests/frontends/mpd/{ => protocol}/regression_test.py (50%) diff --git a/tests/frontends/mpd/regression_test.py b/tests/frontends/mpd/protocol/regression_test.py similarity index 50% rename from tests/frontends/mpd/regression_test.py rename to tests/frontends/mpd/protocol/regression_test.py index f786cf0a..1b257d8b 100644 --- a/tests/frontends/mpd/regression_test.py +++ b/tests/frontends/mpd/protocol/regression_test.py @@ -1,12 +1,10 @@ import random -import unittest -from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpd import dispatcher -from mopidy.mixers.dummy import DummyMixer from mopidy.models import Track -class IssueGH17RegressionTest(unittest.TestCase): +from tests.frontends.mpd import protocol + +class IssueGH17RegressionTest(protocol.BaseTestCase): """ The issue: http://github.com/mopidy/mopidy/issues#issue/17 @@ -16,36 +14,27 @@ class IssueGH17RegressionTest(unittest.TestCase): - Turn on random mode - Press next until you get to the unplayable track """ - - def setUp(self): - self.backend = DummyBackend.start().proxy() + def test(self): self.backend.current_playlist.append([ Track(uri='a'), Track(uri='b'), None, Track(uri='d'), Track(uri='e'), Track(uri='f')]) - self.mixer = DummyMixer.start().proxy() - self.mpd = dispatcher.MpdDispatcher() - - def tearDown(self): - self.backend.stop().get() - self.mixer.stop().get() - - def test(self): random.seed(1) # Playlist order: abcfde - self.mpd.handle_request(u'play') + + self.sendRequest(u'play') self.assertEquals('a', self.backend.playback.current_track.get().uri) - self.mpd.handle_request(u'random "1"') - self.mpd.handle_request(u'next') + self.sendRequest(u'random "1"') + self.sendRequest(u'next') self.assertEquals('b', self.backend.playback.current_track.get().uri) - self.mpd.handle_request(u'next') + self.sendRequest(u'next') # Should now be at track 'c', but playback fails and it skips ahead self.assertEquals('f', self.backend.playback.current_track.get().uri) - self.mpd.handle_request(u'next') + self.sendRequest(u'next') self.assertEquals('d', self.backend.playback.current_track.get().uri) - self.mpd.handle_request(u'next') + self.sendRequest(u'next') self.assertEquals('e', self.backend.playback.current_track.get().uri) -class IssueGH18RegressionTest(unittest.TestCase): +class IssueGH18RegressionTest(protocol.BaseTestCase): """ The issue: http://github.com/mopidy/mopidy/issues#issue/18 @@ -56,38 +45,30 @@ class IssueGH18RegressionTest(unittest.TestCase): At this point it gives the same song over and over. """ - def setUp(self): - self.backend = DummyBackend.start().proxy() + def test(self): self.backend.current_playlist.append([ Track(uri='a'), Track(uri='b'), Track(uri='c'), Track(uri='d'), Track(uri='e'), Track(uri='f')]) - self.mixer = DummyMixer.start().proxy() - self.mpd = dispatcher.MpdDispatcher() - - def tearDown(self): - self.backend.stop().get() - self.mixer.stop().get() - - def test(self): random.seed(1) - self.mpd.handle_request(u'play') - self.mpd.handle_request(u'random "1"') - self.mpd.handle_request(u'next') - self.mpd.handle_request(u'random "0"') - self.mpd.handle_request(u'next') - self.mpd.handle_request(u'next') + self.sendRequest(u'play') + self.sendRequest(u'random "1"') + self.sendRequest(u'next') + self.sendRequest(u'random "0"') + self.sendRequest(u'next') + + self.sendRequest(u'next') cp_track_1 = self.backend.playback.current_cp_track.get() - self.mpd.handle_request(u'next') + self.sendRequest(u'next') cp_track_2 = self.backend.playback.current_cp_track.get() - self.mpd.handle_request(u'next') + self.sendRequest(u'next') cp_track_3 = self.backend.playback.current_cp_track.get() self.assertNotEqual(cp_track_1, cp_track_2) self.assertNotEqual(cp_track_2, cp_track_3) -class IssueGH22RegressionTest(unittest.TestCase): +class IssueGH22RegressionTest(protocol.BaseTestCase): """ The issue: http://github.com/mopidy/mopidy/issues/#issue/22 @@ -100,32 +81,24 @@ class IssueGH22RegressionTest(unittest.TestCase): playlist, press next until it crashes. """ - def setUp(self): - self.backend = DummyBackend.start().proxy() + def test(self): self.backend.current_playlist.append([ Track(uri='a'), Track(uri='b'), Track(uri='c'), Track(uri='d'), Track(uri='e'), Track(uri='f')]) - self.mixer = DummyMixer.start().proxy() - self.mpd = dispatcher.MpdDispatcher() - - def tearDown(self): - self.backend.stop().get() - self.mixer.stop().get() - - def test(self): random.seed(1) - self.mpd.handle_request(u'play') - self.mpd.handle_request(u'random "1"') - self.mpd.handle_request(u'deleteid "1"') - self.mpd.handle_request(u'deleteid "2"') - self.mpd.handle_request(u'deleteid "3"') - self.mpd.handle_request(u'deleteid "4"') - self.mpd.handle_request(u'deleteid "5"') - self.mpd.handle_request(u'deleteid "6"') - self.mpd.handle_request(u'status') + + self.sendRequest(u'play') + self.sendRequest(u'random "1"') + self.sendRequest(u'deleteid "1"') + self.sendRequest(u'deleteid "2"') + self.sendRequest(u'deleteid "3"') + self.sendRequest(u'deleteid "4"') + self.sendRequest(u'deleteid "5"') + self.sendRequest(u'deleteid "6"') + self.sendRequest(u'status') -class IssueGH69RegressionTest(unittest.TestCase): +class IssueGH69RegressionTest(protocol.BaseTestCase): """ The issue: https://github.com/mopidy/mopidy/issues#issue/69 @@ -136,23 +109,14 @@ class IssueGH69RegressionTest(unittest.TestCase): The status response now contains "song: None". """ - def setUp(self): - self.backend = DummyBackend.start().proxy() + def test(self): + self.backend.stored_playlists.create('foo') self.backend.current_playlist.append([ Track(uri='a'), Track(uri='b'), Track(uri='c'), Track(uri='d'), Track(uri='e'), Track(uri='f')]) - self.backend.stored_playlists.create('foo') - self.mixer = DummyMixer.start().proxy() - self.mpd = dispatcher.MpdDispatcher() - def tearDown(self): - self.backend.stop().get() - self.mixer.stop().get() - - def test(self): - self.mpd.handle_request(u'play') - self.mpd.handle_request(u'stop') - self.mpd.handle_request(u'clear') - self.mpd.handle_request(u'load "foo"') - response = self.mpd.handle_request(u'status') - self.assert_('song: None' not in response) + self.sendRequest(u'play') + self.sendRequest(u'stop') + self.sendRequest(u'clear') + self.sendRequest(u'load "foo"') + self.assertNotInResponse('song: None') From b445af7cfb495f31396cca52cde96bb1d5c9aba5 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Wed, 20 Jul 2011 20:09:58 +0200 Subject: [PATCH 20/43] Migrate reflection_test --- tests/frontends/mpd/protocol/__init__.py | 2 + .../frontends/mpd/protocol/reflection_test.py | 66 ++++++++++++++++ tests/frontends/mpd/reflection_test.py | 79 ------------------- 3 files changed, 68 insertions(+), 79 deletions(-) create mode 100644 tests/frontends/mpd/protocol/reflection_test.py delete mode 100644 tests/frontends/mpd/reflection_test.py diff --git a/tests/frontends/mpd/protocol/__init__.py b/tests/frontends/mpd/protocol/__init__.py index fd56c32e..f26b26fd 100644 --- a/tests/frontends/mpd/protocol/__init__.py +++ b/tests/frontends/mpd/protocol/__init__.py @@ -1,6 +1,7 @@ import unittest import mock +from mopidy import settings from mopidy.backends import dummy as backend from mopidy.frontends import mpd from mopidy.frontends.mpd import dispatcher @@ -32,6 +33,7 @@ class BaseTestCase(unittest.TestCase): def tearDown(self): self.backend.stop().get() self.mixer.stop().get() + settings.runtime.clear() def sendRequest(self, request, clear=False): self.connection.response = [] diff --git a/tests/frontends/mpd/protocol/reflection_test.py b/tests/frontends/mpd/protocol/reflection_test.py new file mode 100644 index 00000000..315e3051 --- /dev/null +++ b/tests/frontends/mpd/protocol/reflection_test.py @@ -0,0 +1,66 @@ +from mopidy import settings + +from tests.frontends.mpd import protocol + +class ReflectionHandlerTest(protocol.BaseTestCase): + def test_commands_returns_list_of_all_commands(self): + self.sendRequest(u'commands') + # Check if some random commands are included + self.assertInResponse(u'command: commands') + self.assertInResponse(u'command: play') + self.assertInResponse(u'command: status') + # Check if commands you do not have access to are not present + self.assertNotInResponse(u'command: kill') + # Check if the blacklisted commands are not present + self.assertNotInResponse(u'command: command_list_begin') + self.assertNotInResponse(u'command: command_list_ok_begin') + self.assertNotInResponse(u'command: command_list_end') + self.assertNotInResponse(u'command: idle') + self.assertNotInResponse(u'command: noidle') + self.assertNotInResponse(u'command: sticker') + self.assertInResponse(u'OK') + + def test_commands_show_less_if_auth_required_and_not_authed(self): + settings.MPD_SERVER_PASSWORD = u'secret' + self.sendRequest(u'commands') + # Not requiring auth + self.assertInResponse(u'command: close') + self.assertInResponse(u'command: commands') + self.assertInResponse(u'command: notcommands') + self.assertInResponse(u'command: password') + self.assertInResponse(u'command: ping') + # Requiring auth + self.assertNotInResponse(u'command: play') + self.assertNotInResponse(u'command: status') + + def test_decoders(self): + self.sendRequest(u'decoders') + self.assertInResponse(u'ACK [0@0] {} Not implemented') + + def test_notcommands_returns_only_kill_and_ok(self): + response = self.sendRequest(u'notcommands') + self.assertEqual(2, len(response)) + self.assertInResponse(u'command: kill') + self.assertInResponse(u'OK') + + def test_notcommands_returns_more_if_auth_required_and_not_authed(self): + settings.MPD_SERVER_PASSWORD = u'secret' + self.sendRequest(u'notcommands') + # Not requiring auth + self.assertNotInResponse(u'command: close') + self.assertNotInResponse(u'command: commands') + self.assertNotInResponse(u'command: notcommands') + self.assertNotInResponse(u'command: password') + self.assertNotInResponse(u'command: ping') + # Requiring auth + self.assertInResponse(u'command: play') + self.assertInResponse(u'command: status') + + def test_tagtypes(self): + self.sendRequest(u'tagtypes') + self.assertInResponse(u'OK') + + def test_urlhandlers(self): + self.sendRequest(u'urlhandlers') + self.assertInResponse(u'OK') + self.assertInResponse(u'handler: dummy') diff --git a/tests/frontends/mpd/reflection_test.py b/tests/frontends/mpd/reflection_test.py deleted file mode 100644 index c4fd632a..00000000 --- a/tests/frontends/mpd/reflection_test.py +++ /dev/null @@ -1,79 +0,0 @@ -import unittest - -from mopidy import settings -from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpd.dispatcher import MpdDispatcher -from mopidy.mixers.dummy import DummyMixer - -class ReflectionHandlerTest(unittest.TestCase): - def setUp(self): - self.backend = DummyBackend.start().proxy() - self.mixer = DummyMixer.start().proxy() - self.dispatcher = MpdDispatcher() - - def tearDown(self): - settings.runtime.clear() - self.backend.stop().get() - self.mixer.stop().get() - - def test_commands_returns_list_of_all_commands(self): - result = self.dispatcher.handle_request(u'commands') - # Check if some random commands are included - self.assert_(u'command: commands' in result) - self.assert_(u'command: play' in result) - self.assert_(u'command: status' in result) - # Check if commands you do not have access to are not present - self.assert_(u'command: kill' not in result) - # Check if the blacklisted commands are not present - self.assert_(u'command: command_list_begin' not in result) - self.assert_(u'command: command_list_ok_begin' not in result) - self.assert_(u'command: command_list_end' not in result) - self.assert_(u'command: idle' not in result) - self.assert_(u'command: noidle' not in result) - self.assert_(u'command: sticker' not in result) - self.assert_(u'OK' in result) - - def test_commands_show_less_if_auth_required_and_not_authed(self): - settings.MPD_SERVER_PASSWORD = u'secret' - result = self.dispatcher.handle_request(u'commands') - # Not requiring auth - self.assert_(u'command: close' in result, result) - self.assert_(u'command: commands' in result, result) - self.assert_(u'command: notcommands' in result, result) - self.assert_(u'command: password' in result, result) - self.assert_(u'command: ping' in result, result) - # Requiring auth - self.assert_(u'command: play' not in result, result) - self.assert_(u'command: status' not in result, result) - - def test_decoders(self): - result = self.dispatcher.handle_request(u'decoders') - self.assert_(u'ACK [0@0] {} Not implemented' in result) - - def test_notcommands_returns_only_kill_and_ok(self): - result = self.dispatcher.handle_request(u'notcommands') - self.assertEqual(2, len(result)) - self.assert_(u'command: kill' in result) - self.assert_(u'OK' in result) - - def test_notcommands_returns_more_if_auth_required_and_not_authed(self): - settings.MPD_SERVER_PASSWORD = u'secret' - result = self.dispatcher.handle_request(u'notcommands') - # Not requiring auth - self.assert_(u'command: close' not in result, result) - self.assert_(u'command: commands' not in result, result) - self.assert_(u'command: notcommands' not in result, result) - self.assert_(u'command: password' not in result, result) - self.assert_(u'command: ping' not in result, result) - # Requiring auth - self.assert_(u'command: play' in result, result) - self.assert_(u'command: status' in result, result) - - def test_tagtypes(self): - result = self.dispatcher.handle_request(u'tagtypes') - self.assert_(u'OK' in result) - - def test_urlhandlers(self): - result = self.dispatcher.handle_request(u'urlhandlers') - self.assert_(u'OK' in result) - self.assert_(u'handler: dummy' in result) From f88a0a00908e7419daf5a7f975d179b4f72e205f Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Wed, 20 Jul 2011 20:23:14 +0200 Subject: [PATCH 21/43] Migrate connection_test and support any number of blanks as empty command --- mopidy/frontends/mpd/protocol/empty.py | 2 +- tests/frontends/mpd/connection_test.py | 53 ------------------- .../frontends/mpd/protocol/connection_test.py | 43 +++++++++++++++ 3 files changed, 44 insertions(+), 54 deletions(-) delete mode 100644 tests/frontends/mpd/connection_test.py create mode 100644 tests/frontends/mpd/protocol/connection_test.py diff --git a/mopidy/frontends/mpd/protocol/empty.py b/mopidy/frontends/mpd/protocol/empty.py index 0e418551..33b3bd9f 100644 --- a/mopidy/frontends/mpd/protocol/empty.py +++ b/mopidy/frontends/mpd/protocol/empty.py @@ -1,6 +1,6 @@ from mopidy.frontends.mpd.protocol import handle_request -@handle_request(r'^$') +@handle_request(r'^\s*$') def empty(context): """The original MPD server returns ``OK`` on an empty request.""" pass diff --git a/tests/frontends/mpd/connection_test.py b/tests/frontends/mpd/connection_test.py deleted file mode 100644 index 82debabb..00000000 --- a/tests/frontends/mpd/connection_test.py +++ /dev/null @@ -1,53 +0,0 @@ -import mock -import unittest - -from mopidy import settings -from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpd import MpdSession -from mopidy.frontends.mpd.dispatcher import MpdDispatcher -from mopidy.mixers.dummy import DummyMixer - -class ConnectionHandlerTest(unittest.TestCase): - def setUp(self): - self.backend = DummyBackend.start().proxy() - self.mixer = DummyMixer.start().proxy() - self.session = mock.Mock(spec=MpdSession) - self.dispatcher = MpdDispatcher(session=self.session) - - def tearDown(self): - self.backend.stop().get() - self.mixer.stop().get() - settings.runtime.clear() - - def test_close_closes_the_client_connection(self): - result = self.dispatcher.handle_request(u'close') - self.assert_(self.session.close.called, - u'Should call close() on MpdSession') - self.assert_(u'OK' in result) - - def test_empty_request(self): - result = self.dispatcher.handle_request(u'') - self.assert_(u'OK' in result) - - def test_kill(self): - result = self.dispatcher.handle_request(u'kill') - self.assert_(u'ACK [4@0] {kill} you don\'t have permission for "kill"' in result) - - def test_valid_password_is_accepted(self): - settings.MPD_SERVER_PASSWORD = u'topsecret' - result = self.dispatcher.handle_request(u'password "topsecret"') - self.assert_(u'OK' in result) - - def test_invalid_password_is_not_accepted(self): - settings.MPD_SERVER_PASSWORD = u'topsecret' - result = self.dispatcher.handle_request(u'password "secret"') - self.assert_(u'ACK [3@0] {password} incorrect password' in result) - - def test_any_password_is_not_accepted_when_password_check_turned_off(self): - settings.MPD_SERVER_PASSWORD = None - result = self.dispatcher.handle_request(u'password "secret"') - self.assert_(u'ACK [3@0] {password} incorrect password' in result) - - def test_ping(self): - result = self.dispatcher.handle_request(u'ping') - self.assert_(u'OK' in result) diff --git a/tests/frontends/mpd/protocol/connection_test.py b/tests/frontends/mpd/protocol/connection_test.py new file mode 100644 index 00000000..33b5a1a2 --- /dev/null +++ b/tests/frontends/mpd/protocol/connection_test.py @@ -0,0 +1,43 @@ +from mock import patch + +from mopidy import settings + +from tests.frontends.mpd import protocol + +class ConnectionHandlerTest(protocol.BaseTestCase): + def test_close_closes_the_client_connection(self): + with patch.object(self.session, 'close') as close_mock: + response = self.sendRequest(u'close') + close_mock.assertEqualResponsecalled_once_with() + self.assertEqualResponse(u'OK') + + def test_empty_request(self): + self.sendRequest(u'') + self.assertEqualResponse(u'OK') + + self.sendRequest(u' ') + self.assertEqualResponse(u'OK') + + def test_kill(self): + self.sendRequest(u'kill') + self.assertEqualResponse( + u'ACK [4@0] {kill} you don\'t have permission for "kill"') + + def test_valid_password_is_accepted(self): + settings.MPD_SERVER_PASSWORD = u'topsecret' + self.sendRequest(u'password "topsecret"') + self.assertEqualResponse(u'OK') + + def test_invalid_password_is_not_accepted(self): + settings.MPD_SERVER_PASSWORD = u'topsecret' + self.sendRequest(u'password "secret"') + self.assertEqualResponse(u'ACK [3@0] {password} incorrect password') + + def test_any_password_is_not_accepted_when_password_check_turned_off(self): + settings.MPD_SERVER_PASSWORD = None + self.sendRequest(u'password "secret"') + self.assertEqualResponse(u'ACK [3@0] {password} incorrect password') + + def test_ping(self): + self.sendRequest(u'ping') + self.assertEqualResponse(u'OK') From 15e6f1a6ca18be540b8515c70a8e931e2bf831d1 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Wed, 20 Jul 2011 20:27:38 +0200 Subject: [PATCH 22/43] Cleanup imports and dispatcher assignment in protocol.BaseTestCase --- tests/frontends/mpd/protocol/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/frontends/mpd/protocol/__init__.py b/tests/frontends/mpd/protocol/__init__.py index f26b26fd..77825a6e 100644 --- a/tests/frontends/mpd/protocol/__init__.py +++ b/tests/frontends/mpd/protocol/__init__.py @@ -4,9 +4,7 @@ import mock from mopidy import settings from mopidy.backends import dummy as backend from mopidy.frontends import mpd -from mopidy.frontends.mpd import dispatcher from mopidy.mixers import dummy as mixer -from mopidy.utils import network class MockConnetion(mock.Mock): @@ -25,10 +23,10 @@ class BaseTestCase(unittest.TestCase): def setUp(self): self.backend = backend.DummyBackend.start().proxy() self.mixer = mixer.DummyMixer.start().proxy() - self.dispatcher = dispatcher.MpdDispatcher() self.connection = MockConnetion() self.session = mpd.MpdSession(self.connection) + self.dispatcher = self.session.dispatcher def tearDown(self): self.backend.stop().get() From df66a4234b58be56814df4c3722617f0f845576c Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Wed, 20 Jul 2011 20:30:50 +0200 Subject: [PATCH 23/43] Migrate command_list_test --- tests/frontends/mpd/command_list_test.py | 63 ------------------- .../mpd/protocol/command_list_test.py | 53 ++++++++++++++++ 2 files changed, 53 insertions(+), 63 deletions(-) delete mode 100644 tests/frontends/mpd/command_list_test.py create mode 100644 tests/frontends/mpd/protocol/command_list_test.py diff --git a/tests/frontends/mpd/command_list_test.py b/tests/frontends/mpd/command_list_test.py deleted file mode 100644 index 8fd4c828..00000000 --- a/tests/frontends/mpd/command_list_test.py +++ /dev/null @@ -1,63 +0,0 @@ -import unittest - -from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpd import dispatcher -from mopidy.mixers.dummy import DummyMixer - -class CommandListsTest(unittest.TestCase): - def setUp(self): - self.b = DummyBackend.start().proxy() - self.mixer = DummyMixer.start().proxy() - self.dispatcher = dispatcher.MpdDispatcher() - - def tearDown(self): - self.b.stop().get() - self.mixer.stop().get() - - def test_command_list_begin(self): - result = self.dispatcher.handle_request(u'command_list_begin') - self.assertEquals(result, []) - - def test_command_list_end(self): - self.dispatcher.handle_request(u'command_list_begin') - result = self.dispatcher.handle_request(u'command_list_end') - self.assert_(u'OK' in result) - - def test_command_list_end_without_start_first_is_an_unknown_command(self): - result = self.dispatcher.handle_request(u'command_list_end') - self.assertEquals(result[0], - u'ACK [5@0] {} unknown command "command_list_end"') - - def test_command_list_with_ping(self): - self.dispatcher.handle_request(u'command_list_begin') - self.assertEqual([], self.dispatcher.command_list) - self.assertEqual(False, self.dispatcher.command_list_ok) - self.dispatcher.handle_request(u'ping') - self.assert_(u'ping' in self.dispatcher.command_list) - result = self.dispatcher.handle_request(u'command_list_end') - self.assert_(u'OK' in result) - self.assertEqual(False, self.dispatcher.command_list) - - def test_command_list_with_error_returns_ack_with_correct_index(self): - self.dispatcher.handle_request(u'command_list_begin') - self.dispatcher.handle_request(u'play') # Known command - self.dispatcher.handle_request(u'paly') # Unknown command - result = self.dispatcher.handle_request(u'command_list_end') - self.assertEqual(len(result), 1, result) - self.assertEqual(result[0], u'ACK [5@1] {} unknown command "paly"') - - def test_command_list_ok_begin(self): - result = self.dispatcher.handle_request(u'command_list_ok_begin') - self.assertEquals(result, []) - - def test_command_list_ok_with_ping(self): - self.dispatcher.handle_request(u'command_list_ok_begin') - self.assertEqual([], self.dispatcher.command_list) - self.assertEqual(True, self.dispatcher.command_list_ok) - self.dispatcher.handle_request(u'ping') - self.assert_(u'ping' in self.dispatcher.command_list) - result = self.dispatcher.handle_request(u'command_list_end') - self.assert_(u'list_OK' in result) - self.assert_(u'OK' in result) - self.assertEqual(False, self.dispatcher.command_list) - self.assertEqual(False, self.dispatcher.command_list_ok) diff --git a/tests/frontends/mpd/protocol/command_list_test.py b/tests/frontends/mpd/protocol/command_list_test.py new file mode 100644 index 00000000..9b5ef690 --- /dev/null +++ b/tests/frontends/mpd/protocol/command_list_test.py @@ -0,0 +1,53 @@ +from tests.frontends.mpd import protocol + +class CommandListsTest(protocol.BaseTestCase): + def test_command_list_begin(self): + response = self.sendRequest(u'command_list_begin') + self.assertEquals([], response) + + def test_command_list_end(self): + self.sendRequest(u'command_list_begin') + self.sendRequest(u'command_list_end') + self.assertInResponse(u'OK') + + def test_command_list_end_without_start_first_is_an_unknown_command(self): + self.sendRequest(u'command_list_end') + self.assertEqualResponse( + u'ACK [5@0] {} unknown command "command_list_end"') + + def test_command_list_with_ping(self): + self.sendRequest(u'command_list_begin') + self.assertEqual([], self.dispatcher.command_list) + self.assertEqual(False, self.dispatcher.command_list_ok) + self.sendRequest(u'ping') + self.assert_(u'ping' in self.dispatcher.command_list) + self.sendRequest(u'command_list_end') + self.assertInResponse(u'OK') + self.assertEqual(False, self.dispatcher.command_list) + + def test_command_list_with_error_returns_ack_with_correct_index(self): + self.sendRequest(u'command_list_begin') + self.sendRequest(u'play') # Known command + self.sendRequest(u'paly') # Unknown command + self.sendRequest(u'command_list_end') + self.assertEqualResponse(u'ACK [5@1] {} unknown command "paly"') + + def test_command_list_ok_begin(self): + response = self.sendRequest(u'command_list_ok_begin') + self.assertEquals([], response) + + def test_command_list_ok_with_ping(self): + self.sendRequest(u'command_list_ok_begin') + self.assertEqual([], self.dispatcher.command_list) + self.assertEqual(True, self.dispatcher.command_list_ok) + self.sendRequest(u'ping') + self.assert_(u'ping' in self.dispatcher.command_list) + self.sendRequest(u'command_list_end') + self.assertInResponse(u'list_OK') + self.assertInResponse(u'OK') + self.assertEqual(False, self.dispatcher.command_list) + self.assertEqual(False, self.dispatcher.command_list_ok) + + # FIXME this should also include the special handling of idle within a + # command list. That is that once a idle/noidle command is found inside a + # commad list, the rest of the list seems to be ignored. From b5a32a1dc3fda065a1bbc0da18378439a6436b1f Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Wed, 20 Jul 2011 20:35:27 +0200 Subject: [PATCH 24/43] Migrate authentication_test --- .../mpd/{ => protocol}/authentication_test.py | 56 +++++++++---------- 1 file changed, 27 insertions(+), 29 deletions(-) rename tests/frontends/mpd/{ => protocol}/authentication_test.py (53%) diff --git a/tests/frontends/mpd/authentication_test.py b/tests/frontends/mpd/protocol/authentication_test.py similarity index 53% rename from tests/frontends/mpd/authentication_test.py rename to tests/frontends/mpd/protocol/authentication_test.py index fb32ea54..a1487cf9 100644 --- a/tests/frontends/mpd/authentication_test.py +++ b/tests/frontends/mpd/protocol/authentication_test.py @@ -1,63 +1,61 @@ -import mock -import unittest - from mopidy import settings -from mopidy.frontends.mpd import MpdSession -from mopidy.frontends.mpd.dispatcher import MpdDispatcher -class AuthenticationTest(unittest.TestCase): - def setUp(self): - self.session = mock.Mock(spec=MpdSession) - self.dispatcher = MpdDispatcher(session=self.session) - - def tearDown(self): - settings.runtime.clear() +from tests.frontends.mpd import protocol +class AuthenticationTest(protocol.BaseTestCase): def test_authentication_with_valid_password_is_accepted(self): settings.MPD_SERVER_PASSWORD = u'topsecret' - response = self.dispatcher.handle_request(u'password "topsecret"') + + self.sendRequest(u'password "topsecret"') self.assertTrue(self.dispatcher.authenticated) - self.assert_(u'OK' in response) + self.assertInResponse(u'OK') def test_authentication_with_invalid_password_is_not_accepted(self): settings.MPD_SERVER_PASSWORD = u'topsecret' - response = self.dispatcher.handle_request(u'password "secret"') + + self.sendRequest(u'password "secret"') self.assertFalse(self.dispatcher.authenticated) - self.assert_(u'ACK [3@0] {password} incorrect password' in response) + self.assertEqualResponse(u'ACK [3@0] {password} incorrect password') def test_authentication_with_anything_when_password_check_turned_off(self): settings.MPD_SERVER_PASSWORD = None - response = self.dispatcher.handle_request(u'any request at all') + + self.sendRequest(u'any request at all') self.assertTrue(self.dispatcher.authenticated) - self.assert_('ACK [5@0] {} unknown command "any"' in response) + self.assertEqualResponse('ACK [5@0] {} unknown command "any"') def test_anything_when_not_authenticated_should_fail(self): settings.MPD_SERVER_PASSWORD = u'topsecret' - response = self.dispatcher.handle_request(u'any request at all') + + self.sendRequest(u'any request at all') self.assertFalse(self.dispatcher.authenticated) - self.assert_( - u'ACK [4@0] {any} you don\'t have permission for "any"' in response) + self.assertEqualResponse( + u'ACK [4@0] {any} you don\'t have permission for "any"') def test_close_is_allowed_without_authentication(self): settings.MPD_SERVER_PASSWORD = u'topsecret' - response = self.dispatcher.handle_request(u'close') + + self.sendRequest(u'close') self.assertFalse(self.dispatcher.authenticated) - self.assert_(u'OK' in response) + self.assertInResponse(u'OK') def test_commands_is_allowed_without_authentication(self): settings.MPD_SERVER_PASSWORD = u'topsecret' - response = self.dispatcher.handle_request(u'commands') + + self.sendRequest(u'commands') self.assertFalse(self.dispatcher.authenticated) - self.assert_(u'OK' in response) + self.assertInResponse(u'OK') def test_notcommands_is_allowed_without_authentication(self): settings.MPD_SERVER_PASSWORD = u'topsecret' - response = self.dispatcher.handle_request(u'notcommands') + + self.sendRequest(u'notcommands') self.assertFalse(self.dispatcher.authenticated) - self.assert_(u'OK' in response) + self.assertInResponse(u'OK') def test_ping_is_allowed_without_authentication(self): settings.MPD_SERVER_PASSWORD = u'topsecret' - response = self.dispatcher.handle_request(u'ping') + + self.sendRequest(u'ping') self.assertFalse(self.dispatcher.authenticated) - self.assert_(u'OK' in response) + self.assertInResponse(u'OK') From 08d486785da5a3a90782278c6cabd74bae28ff0b Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Fri, 22 Jul 2011 22:50:36 +0200 Subject: [PATCH 25/43] Basic working version of idle command --- mopidy/frontends/mpd/dispatcher.py | 66 +++++- mopidy/frontends/mpd/protocol/status.py | 6 +- tests/frontends/mpd/protocol/__init__.py | 4 + tests/frontends/mpd/protocol/idle_test.py | 210 ++++++++++++++++++ .../frontends/mpd/protocol/reflection_test.py | 4 +- tests/frontends/mpd/protocol/status_test.py | 15 -- 6 files changed, 283 insertions(+), 22 deletions(-) create mode 100644 tests/frontends/mpd/protocol/idle_test.py diff --git a/mopidy/frontends/mpd/dispatcher.py b/mopidy/frontends/mpd/dispatcher.py index 6cc05bec..a36d38e3 100644 --- a/mopidy/frontends/mpd/dispatcher.py +++ b/mopidy/frontends/mpd/dispatcher.py @@ -20,6 +20,10 @@ from mopidy.utils import flatten logger = logging.getLogger('mopidy.frontends.mpd.dispatcher') +#: Subsystems that can be registered with idle command. +SUBSYSTEMS = ['database', 'mixer', 'options', 'output', + 'player', 'playlist', 'stored_playlist', 'update', ] + class MpdDispatcher(object): """ The MPD session feeds the MPD dispatcher with requests. The dispatcher @@ -32,6 +36,8 @@ class MpdDispatcher(object): self.command_list = False self.command_list_ok = False self.command_list_index = None + self.subscriptions = set() + self.events = set() self.context = MpdContext(self, session=session) def handle_request(self, request, current_command_list_index=None): @@ -42,13 +48,26 @@ class MpdDispatcher(object): self._catch_mpd_ack_errors_filter, self._authenticate_filter, self._command_list_filter, + self._idle_filter, self._add_ok_filter, self._call_handler_filter, ] return self._call_next_filter(request, response, filter_chain) def handle_idle(self, subsystem): - logger.debug(u'Got idle event for %s', subsystem) + self.events.add(subsystem) + + subsystems = self.subscriptions.intersection(self.events) + if not subsystems: + return + + response = [] + for subsystem in subsystems: + response.append(u'changed: %s' % subsystem) + response.append(u'OK') + self.subscriptions = set() + self.events = set() + self.context.session.send_lines(response) def _call_next_filter(self, request, response, filter_chain): if filter_chain: @@ -111,17 +130,60 @@ class MpdDispatcher(object): and request != u'command_list_end') + ### Filter: idle + + def _idle_filter(self, request, response, filter_chain): + if re.match(r'^noidle$', request): + if not self.subscriptions: + return [] + self.subscriptions = set() + self.events = set() + self.context.session.connection.enable_timeout() + return [u'OK'] + + if self.subscriptions: + self.context.session.close() + return [] + + if re.match(r'^idle( .+)?$', request): + for subsystem in self._extract_subsystems(request): + self.subscriptions.add(subsystem) + + subsystems = self.subscriptions.intersection(self.events) + if subsystems: + for subsystem in subsystems: + response.append(u'changed: %s' % subsystem) + self.events = set() + self.subscriptions = set() + response.append(u'OK') + return response + else: + self.context.session.connection.disable_timeout() + return [] + + return self._call_next_filter(request, response, filter_chain) + + def _extract_subsystems(self, request): + match = re.match(r'^idle (?P.+)$', request) + if not match: + return SUBSYSTEMS + return match.groupdict()['subsystems'].split(' ') + + ### Filter: add OK def _add_ok_filter(self, request, response, filter_chain): response = self._call_next_filter(request, response, filter_chain) - if not self._has_error(response): + if not self._has_error(response) and not self._is_idle(request): response.append(u'OK') return response def _has_error(self, response): return response and response[-1].startswith(u'ACK') + def _is_idle(self, request): + return request.startswith('idle') + ### Filter: call handler diff --git a/mopidy/frontends/mpd/protocol/status.py b/mopidy/frontends/mpd/protocol/status.py index abbb8d7f..4a961e76 100644 --- a/mopidy/frontends/mpd/protocol/status.py +++ b/mopidy/frontends/mpd/protocol/status.py @@ -32,8 +32,8 @@ def currentsong(context): position=context.backend.playback.current_playlist_position.get(), cpid=current_cp_track.cpid) -@handle_request(r'^idle$') -@handle_request(r'^idle (?P.+)$') +#@handle_request(r'^idle$') +#@handle_request(r'^idle (?P.+)$') def idle(context, subsystems=None): """ *musicpd.org, status section:* @@ -69,7 +69,7 @@ def idle(context, subsystems=None): """ pass # TODO -@handle_request(r'^noidle$') +#@handle_request(r'^noidle$') def noidle(context): """See :meth:`_status_idle`.""" pass # TODO diff --git a/tests/frontends/mpd/protocol/__init__.py b/tests/frontends/mpd/protocol/__init__.py index 77825a6e..68668bc2 100644 --- a/tests/frontends/mpd/protocol/__init__.py +++ b/tests/frontends/mpd/protocol/__init__.py @@ -27,6 +27,7 @@ class BaseTestCase(unittest.TestCase): self.connection = MockConnetion() self.session = mpd.MpdSession(self.connection) self.dispatcher = self.session.dispatcher + self.context = self.dispatcher.context def tearDown(self): self.backend.stop().get() @@ -38,6 +39,9 @@ class BaseTestCase(unittest.TestCase): self.session.on_line_received(request) return self.connection.response + def assertNoResponse(self): + self.assertEqual([], self.connection.response) + def assertInResponse(self, value): self.assert_(value in self.connection.response, u'Did not find %s ' 'in %s' % (repr(value), repr(self.connection.response))) diff --git a/tests/frontends/mpd/protocol/idle_test.py b/tests/frontends/mpd/protocol/idle_test.py new file mode 100644 index 00000000..319789bf --- /dev/null +++ b/tests/frontends/mpd/protocol/idle_test.py @@ -0,0 +1,210 @@ +from mock import patch + +from mopidy.frontends.mpd.dispatcher import SUBSYSTEMS +from mopidy.models import Track + +from tests.frontends.mpd import protocol + +class IdleHandlerTest(protocol.BaseTestCase): + def idleEvent(self, subsystem): + self.session.on_idle(subsystem) + + def assertEqualEvents(self, events): + self.assertEqual(set(events), self.dispatcher.events) + + def assertEqualSubscriptions(self, events): + self.assertEqual(set(events), self.dispatcher.subscriptions) + + def assertNoEvents(self): + self.assertEqualEvents([]) + + def assertNoSubscriptions(self): + self.assertEqualSubscriptions([]) + + def test_base_state(self): + self.assertNoSubscriptions() + self.assertNoEvents() + self.assertNoResponse() + + def test_idle(self): + self.sendRequest(u'idle') + self.assertEqualSubscriptions(SUBSYSTEMS) + self.assertNoEvents() + self.assertNoResponse() + + def test_idle_disables_timeout(self): + self.sendRequest(u'idle') + self.connection.disable_timeout.assert_called_once_with() + + def test_noidle(self): + self.sendRequest(u'noidle') + self.assertNoSubscriptions() + self.assertNoEvents() + self.assertNoResponse() + + def test_noidle_does_not_call_enable_timeout(self): + self.sendRequest(u'noidle') + self.assertEqual(0, self.connection.enable_timeout.call_count) + + def test_idle_player(self): + self.sendRequest(u'idle player') + self.assertEqualSubscriptions(['player']) + self.assertNoEvents() + self.assertNoResponse() + + def test_idle_player_playlist(self): + self.sendRequest(u'idle player playlist') + self.assertEqualSubscriptions(['player', 'playlist']) + self.assertNoEvents() + self.assertNoResponse() + + def test_idle_then_noidle(self): + self.sendRequest(u'idle') + self.sendRequest(u'noidle') + self.assertNoSubscriptions() + self.assertNoEvents() + self.assertInResponse(u'OK') + + def test_idle_then_noidle_enables_timeout(self): + self.sendRequest(u'idle') + self.sendRequest(u'noidle') + self.connection.enable_timeout.assert_called_once_with() + + def test_idle_then_play(self): + with patch.object(self.session, 'stop') as stop_mock: + self.sendRequest(u'idle') + self.sendRequest(u'play') + stop_mock.assert_called_once_with() + + def test_idle_then_idle(self): + with patch.object(self.session, 'stop') as stop_mock: + self.sendRequest(u'idle') + self.sendRequest(u'idle') + stop_mock.assert_called_once_with() + + def test_idle_player_then_play(self): + with patch.object(self.session, 'stop') as stop_mock: + self.sendRequest(u'idle player') + self.sendRequest(u'play') + stop_mock.assert_called_once_with() + + def test_idle_then_player(self): + self.sendRequest(u'idle') + self.idleEvent(u'player') + self.assertNoSubscriptions() + self.assertNoEvents() + self.assertInResponse(u'changed: player') + self.assertInResponse(u'OK') + + def test_idle_player_then_event_player(self): + self.sendRequest(u'idle player') + self.idleEvent(u'player') + self.assertNoSubscriptions() + self.assertNoEvents() + self.assertInResponse(u'changed: player') + self.assertInResponse(u'OK') + + def test_idle_player_then_noidle(self): + self.sendRequest(u'idle player') + self.sendRequest(u'noidle') + self.assertNoSubscriptions() + self.assertNoEvents() + self.assertInResponse(u'OK') + + def test_idle_player_playlist_then_noidle(self): + self.sendRequest(u'idle player playlist') + self.sendRequest(u'noidle') + self.assertNoEvents() + self.assertNoSubscriptions() + self.assertInResponse(u'OK') + + def test_idle_player_playlist_then_player(self): + self.sendRequest(u'idle player playlist') + self.idleEvent(u'player') + self.assertNoEvents() + self.assertNoSubscriptions() + self.assertInResponse(u'changed: player') + self.assertNotInResponse(u'changed: playlist') + self.assertInResponse(u'OK') + + def test_idle_playlist_then_player(self): + self.sendRequest(u'idle playlist') + self.idleEvent(u'player') + self.assertEqualEvents(['player']) + self.assertEqualSubscriptions(['playlist']) + self.assertNoResponse() + + def test_idle_playlist_then_player_then_playlist(self): + self.sendRequest(u'idle playlist') + self.idleEvent(u'player') + self.idleEvent(u'playlist') + self.assertNoEvents() + self.assertNoSubscriptions() + self.assertNotInResponse(u'changed: player') + self.assertInResponse(u'changed: playlist') + self.assertInResponse(u'OK') + + def test_player(self): + self.idleEvent(u'player') + self.assertEqualEvents(['player']) + self.assertNoSubscriptions() + self.assertNoResponse() + + def test_player_then_idle_player(self): + self.idleEvent(u'player') + self.sendRequest(u'idle player') + self.assertNoEvents() + self.assertNoSubscriptions() + self.assertInResponse(u'changed: player') + self.assertNotInResponse(u'changed: playlist') + self.assertInResponse(u'OK') + + def test_player_then_playlist(self): + self.idleEvent(u'player') + self.idleEvent(u'playlist') + self.assertEqualEvents(['player', 'playlist']) + self.assertNoSubscriptions() + self.assertNoResponse() + + def test_player_then_idle(self): + self.idleEvent(u'player') + self.sendRequest(u'idle') + self.assertNoEvents() + self.assertNoSubscriptions() + self.assertInResponse(u'changed: player') + self.assertInResponse(u'OK') + + def test_player_then_playlist_then_idle(self): + self.idleEvent(u'player') + self.idleEvent(u'playlist') + self.sendRequest(u'idle') + self.assertNoEvents() + self.assertNoSubscriptions() + self.assertInResponse(u'changed: player') + self.assertInResponse(u'changed: playlist') + self.assertInResponse(u'OK') + + def test_player_then_idle_playlist(self): + self.idleEvent(u'player') + self.sendRequest(u'idle playlist') + self.assertEqualEvents(['player']) + self.assertEqualSubscriptions(['playlist']) + self.assertNoResponse() + + def test_player_then_idle_playlist_then_noidle(self): + self.idleEvent(u'player') + self.sendRequest(u'idle playlist') + self.sendRequest(u'noidle') + self.assertNoEvents() + self.assertNoSubscriptions() + self.assertInResponse(u'OK') + + def test_player_then_playlist_then_idle_playlist(self): + self.idleEvent(u'player') + self.idleEvent(u'playlist') + self.sendRequest(u'idle playlist') + self.assertNoEvents() + self.assertNoSubscriptions() + self.assertNotInResponse(u'changed: player') + self.assertInResponse(u'changed: playlist') + self.assertInResponse(u'OK') diff --git a/tests/frontends/mpd/protocol/reflection_test.py b/tests/frontends/mpd/protocol/reflection_test.py index 315e3051..9cf1ef97 100644 --- a/tests/frontends/mpd/protocol/reflection_test.py +++ b/tests/frontends/mpd/protocol/reflection_test.py @@ -9,14 +9,14 @@ class ReflectionHandlerTest(protocol.BaseTestCase): self.assertInResponse(u'command: commands') self.assertInResponse(u'command: play') self.assertInResponse(u'command: status') + self.assertInResponse(u'command: idle') + self.assertInResponse(u'command: noidle') # Check if commands you do not have access to are not present self.assertNotInResponse(u'command: kill') # Check if the blacklisted commands are not present self.assertNotInResponse(u'command: command_list_begin') self.assertNotInResponse(u'command: command_list_ok_begin') self.assertNotInResponse(u'command: command_list_end') - self.assertNotInResponse(u'command: idle') - self.assertNotInResponse(u'command: noidle') self.assertNotInResponse(u'command: sticker') self.assertInResponse(u'OK') diff --git a/tests/frontends/mpd/protocol/status_test.py b/tests/frontends/mpd/protocol/status_test.py index 6762a4fb..f50ecd24 100644 --- a/tests/frontends/mpd/protocol/status_test.py +++ b/tests/frontends/mpd/protocol/status_test.py @@ -27,21 +27,6 @@ class StatusHandlerTest(protocol.BaseTestCase): self.sendRequest(u'currentsong') self.assertInResponse(u'OK') - def test_idle_without_subsystems(self): - # FIXME this is not the correct behaviour for idle... - self.sendRequest(u'idle') - self.assertInResponse(u'OK') - - def test_idle_with_subsystems(self): - # FIXME this is not the correct behaviour for idle... - self.sendRequest(u'idle database playlist') - self.assertInResponse(u'OK') - - def test_noidle(self): - # FIXME this is not the correct behaviour for idle... - self.sendRequest(u'noidle') - self.assertInResponse(u'OK') - def test_stats_command(self): self.sendRequest(u'stats') self.assertInResponse(u'OK') From 171137504f3e53de5dc54ae4dfc7e29111bb8285 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sat, 23 Jul 2011 02:30:13 +0200 Subject: [PATCH 26/43] Move subscriptions and events into context object --- mopidy/frontends/mpd/dispatcher.py | 36 ++++++++++++++--------- tests/frontends/mpd/protocol/idle_test.py | 4 +-- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/mopidy/frontends/mpd/dispatcher.py b/mopidy/frontends/mpd/dispatcher.py index a36d38e3..ea2fc1e8 100644 --- a/mopidy/frontends/mpd/dispatcher.py +++ b/mopidy/frontends/mpd/dispatcher.py @@ -36,8 +36,6 @@ class MpdDispatcher(object): self.command_list = False self.command_list_ok = False self.command_list_index = None - self.subscriptions = set() - self.events = set() self.context = MpdContext(self, session=session) def handle_request(self, request, current_command_list_index=None): @@ -55,9 +53,10 @@ class MpdDispatcher(object): return self._call_next_filter(request, response, filter_chain) def handle_idle(self, subsystem): - self.events.add(subsystem) + self.context.events.add(subsystem) - subsystems = self.subscriptions.intersection(self.events) + subsystems = self.context.subscriptions.intersection( + self.context.events) if not subsystems: return @@ -65,8 +64,8 @@ class MpdDispatcher(object): for subsystem in subsystems: response.append(u'changed: %s' % subsystem) response.append(u'OK') - self.subscriptions = set() - self.events = set() + self.context.subscriptions = set() + self.context.events = set() self.context.session.send_lines(response) def _call_next_filter(self, request, response, filter_chain): @@ -134,27 +133,28 @@ class MpdDispatcher(object): def _idle_filter(self, request, response, filter_chain): if re.match(r'^noidle$', request): - if not self.subscriptions: + if not self.context.subscriptions: return [] - self.subscriptions = set() - self.events = set() + self.context.subscriptions = set() + self.context.events = set() self.context.session.connection.enable_timeout() return [u'OK'] - if self.subscriptions: + if self.context.subscriptions: self.context.session.close() return [] if re.match(r'^idle( .+)?$', request): for subsystem in self._extract_subsystems(request): - self.subscriptions.add(subsystem) + self.context.subscriptions.add(subsystem) - subsystems = self.subscriptions.intersection(self.events) + subsystems = self.context.subscriptions.intersection( + self.context.events) if subsystems: for subsystem in subsystems: response.append(u'changed: %s' % subsystem) - self.events = set() - self.subscriptions = set() + self.context.events = set() + self.context.subscriptions = set() response.append(u'OK') return response else: @@ -246,9 +246,17 @@ class MpdContext(object): #: The current :class:`mopidy.frontends.mpd.MpdSession`. session = None + #: The active subsystems that have pending events. + events = None + + #: The subsytems that we want to be notified about in idle mode. + subscriptions = None + def __init__(self, dispatcher, session=None): self.dispatcher = dispatcher self.session = session + self.events = set() + self.subscriptions = set() self._backend = None self._mixer = None diff --git a/tests/frontends/mpd/protocol/idle_test.py b/tests/frontends/mpd/protocol/idle_test.py index 319789bf..3efb0cb1 100644 --- a/tests/frontends/mpd/protocol/idle_test.py +++ b/tests/frontends/mpd/protocol/idle_test.py @@ -10,10 +10,10 @@ class IdleHandlerTest(protocol.BaseTestCase): self.session.on_idle(subsystem) def assertEqualEvents(self, events): - self.assertEqual(set(events), self.dispatcher.events) + self.assertEqual(set(events), self.context.events) def assertEqualSubscriptions(self, events): - self.assertEqual(set(events), self.dispatcher.subscriptions) + self.assertEqual(set(events), self.context.subscriptions) def assertNoEvents(self): self.assertEqualEvents([]) From e4ce31a438d5adbb13752c09fd8be5bd38776b37 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sat, 23 Jul 2011 03:28:29 +0200 Subject: [PATCH 27/43] Ensure that empty command does not get added to command list --- mopidy/frontends/mpd/protocol/empty.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mopidy/frontends/mpd/protocol/empty.py b/mopidy/frontends/mpd/protocol/empty.py index 33b3bd9f..4cdafd87 100644 --- a/mopidy/frontends/mpd/protocol/empty.py +++ b/mopidy/frontends/mpd/protocol/empty.py @@ -1,6 +1,6 @@ from mopidy.frontends.mpd.protocol import handle_request -@handle_request(r'^\s*$') +@handle_request(r'^[ ]*$') def empty(context): """The original MPD server returns ``OK`` on an empty request.""" pass From 63dba5553fbc74a26420fa3c5c5557469672c3fa Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sat, 23 Jul 2011 03:32:45 +0200 Subject: [PATCH 28/43] Move idle code from dispatcher to protocol.status module --- mopidy/frontends/mpd/dispatcher.py | 55 ++++++--------------- mopidy/frontends/mpd/protocol/reflection.py | 4 -- mopidy/frontends/mpd/protocol/status.py | 40 +++++++++++++-- tests/frontends/mpd/protocol/idle_test.py | 2 +- 4 files changed, 52 insertions(+), 49 deletions(-) diff --git a/mopidy/frontends/mpd/dispatcher.py b/mopidy/frontends/mpd/dispatcher.py index ea2fc1e8..660f82ec 100644 --- a/mopidy/frontends/mpd/dispatcher.py +++ b/mopidy/frontends/mpd/dispatcher.py @@ -20,10 +20,6 @@ from mopidy.utils import flatten logger = logging.getLogger('mopidy.frontends.mpd.dispatcher') -#: Subsystems that can be registered with idle command. -SUBSYSTEMS = ['database', 'mixer', 'options', 'output', - 'player', 'playlist', 'stored_playlist', 'update', ] - class MpdDispatcher(object): """ The MPD session feeds the MPD dispatcher with requests. The dispatcher @@ -132,59 +128,40 @@ class MpdDispatcher(object): ### Filter: idle def _idle_filter(self, request, response, filter_chain): - if re.match(r'^noidle$', request): - if not self.context.subscriptions: - return [] - self.context.subscriptions = set() - self.context.events = set() - self.context.session.connection.enable_timeout() - return [u'OK'] - - if self.context.subscriptions: + if self._is_currently_idle() and not self._is_noidle(request): + logger.debug(u'Client send us %s, only %s is allowed while in ' + 'the idle state', repr(request), repr('noidle')) self.context.session.close() return [] - if re.match(r'^idle( .+)?$', request): - for subsystem in self._extract_subsystems(request): - self.context.subscriptions.add(subsystem) + if not self._is_currently_idle() and self._is_noidle(request): + return [] - subsystems = self.context.subscriptions.intersection( - self.context.events) - if subsystems: - for subsystem in subsystems: - response.append(u'changed: %s' % subsystem) - self.context.events = set() - self.context.subscriptions = set() - response.append(u'OK') - return response - else: - self.context.session.connection.disable_timeout() - return [] + response = self._call_next_filter(request, response, filter_chain) - return self._call_next_filter(request, response, filter_chain) + if self._is_currently_idle(): + return [] + else: + return response - def _extract_subsystems(self, request): - match = re.match(r'^idle (?P.+)$', request) - if not match: - return SUBSYSTEMS - return match.groupdict()['subsystems'].split(' ') + def _is_currently_idle(self): + return bool(self.context.subscriptions) + + def _is_noidle(self, request): + return re.match(r'^noidle$', request) ### Filter: add OK def _add_ok_filter(self, request, response, filter_chain): response = self._call_next_filter(request, response, filter_chain) - if not self._has_error(response) and not self._is_idle(request): + if not self._has_error(response): response.append(u'OK') return response def _has_error(self, response): return response and response[-1].startswith(u'ACK') - def _is_idle(self, request): - return request.startswith('idle') - - ### Filter: call handler def _call_handler_filter(self, request, response, filter_chain): diff --git a/mopidy/frontends/mpd/protocol/reflection.py b/mopidy/frontends/mpd/protocol/reflection.py index 3618f5e1..dbd76034 100644 --- a/mopidy/frontends/mpd/protocol/reflection.py +++ b/mopidy/frontends/mpd/protocol/reflection.py @@ -27,10 +27,6 @@ def commands(context): command_names.remove('command_list_ok_begin') if 'command_list_end' in command_names: command_names.remove('command_list_end') - if 'idle' in command_names: - command_names.remove('idle') - if 'noidle' in command_names: - command_names.remove('noidle') if 'sticker' in command_names: command_names.remove('sticker') diff --git a/mopidy/frontends/mpd/protocol/status.py b/mopidy/frontends/mpd/protocol/status.py index 4a961e76..444ec0c2 100644 --- a/mopidy/frontends/mpd/protocol/status.py +++ b/mopidy/frontends/mpd/protocol/status.py @@ -4,6 +4,10 @@ from mopidy.backends.base import PlaybackController from mopidy.frontends.mpd.protocol import handle_request from mopidy.frontends.mpd.exceptions import MpdNotImplemented +#: Subsystems that can be registered with idle command. +SUBSYSTEMS = ['database', 'mixer', 'options', 'output', + 'player', 'playlist', 'stored_playlist', 'update', ] + @handle_request(r'^clearerror$') def clearerror(context): """ @@ -32,8 +36,8 @@ def currentsong(context): position=context.backend.playback.current_playlist_position.get(), cpid=current_cp_track.cpid) -#@handle_request(r'^idle$') -#@handle_request(r'^idle (?P.+)$') +@handle_request(r'^idle$') +@handle_request(r'^idle (?P.+)$') def idle(context, subsystems=None): """ *musicpd.org, status section:* @@ -67,12 +71,38 @@ def idle(context, subsystems=None): notifications when something changed in one of the specified subsystems. """ - pass # TODO -#@handle_request(r'^noidle$') + if subsystems: + subsystems = subsystems.split() + else: + subsystems = SUBSYSTEMS + + for subsystem in subsystems: + context.subscriptions.add(subsystem) + + active = context.subscriptions.intersection(context.events) + if not active: + context.session.connection.disable_timeout() + return + + response = [] + context.events = set() + context.subscriptions = set() + + for subsystem in active: + response.append(u'changed: %s' % subsystem) + response.append(u'OK') + + return response + +@handle_request(r'^noidle$') def noidle(context): """See :meth:`_status_idle`.""" - pass # TODO + if not context.subscriptions: + return + context.subscriptions = set() + context.events = set() + context.session.connection.enable_timeout() @handle_request(r'^stats$') def stats(context): diff --git a/tests/frontends/mpd/protocol/idle_test.py b/tests/frontends/mpd/protocol/idle_test.py index 3efb0cb1..0f56cd61 100644 --- a/tests/frontends/mpd/protocol/idle_test.py +++ b/tests/frontends/mpd/protocol/idle_test.py @@ -1,6 +1,6 @@ from mock import patch -from mopidy.frontends.mpd.dispatcher import SUBSYSTEMS +from mopidy.frontends.mpd.protocol.status import SUBSYSTEMS from mopidy.models import Track from tests.frontends.mpd import protocol From 0e58d771cdf332edef1f4a93fb131cd5f4ba434b Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sat, 23 Jul 2011 03:58:26 +0200 Subject: [PATCH 29/43] Make tests check that response only has values once, fixes double OK bug --- mopidy/frontends/mpd/protocol/status.py | 2 -- tests/frontends/mpd/protocol/__init__.py | 5 +++ tests/frontends/mpd/protocol/idle_test.py | 42 +++++++++++------------ 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/mopidy/frontends/mpd/protocol/status.py b/mopidy/frontends/mpd/protocol/status.py index 444ec0c2..5a319a5d 100644 --- a/mopidy/frontends/mpd/protocol/status.py +++ b/mopidy/frontends/mpd/protocol/status.py @@ -91,8 +91,6 @@ def idle(context, subsystems=None): for subsystem in active: response.append(u'changed: %s' % subsystem) - response.append(u'OK') - return response @handle_request(r'^noidle$') diff --git a/tests/frontends/mpd/protocol/__init__.py b/tests/frontends/mpd/protocol/__init__.py index 68668bc2..00314946 100644 --- a/tests/frontends/mpd/protocol/__init__.py +++ b/tests/frontends/mpd/protocol/__init__.py @@ -46,6 +46,11 @@ class BaseTestCase(unittest.TestCase): self.assert_(value in self.connection.response, u'Did not find %s ' 'in %s' % (repr(value), repr(self.connection.response))) + def assertOnceInResponse(self, value): + matched = len([r for r in self.connection.response if r == value]) + self.assertEqual(1, matched, 'Expected to find %s once in %s' % + (repr(value), repr(self.connection.response))) + def assertNotInResponse(self, value): self.assert_(value not in self.connection.response, u'Found %s in %s' % (repr(value), repr(self.connection.response))) diff --git a/tests/frontends/mpd/protocol/idle_test.py b/tests/frontends/mpd/protocol/idle_test.py index 0f56cd61..0f5d4b06 100644 --- a/tests/frontends/mpd/protocol/idle_test.py +++ b/tests/frontends/mpd/protocol/idle_test.py @@ -63,7 +63,7 @@ class IdleHandlerTest(protocol.BaseTestCase): self.sendRequest(u'noidle') self.assertNoSubscriptions() self.assertNoEvents() - self.assertInResponse(u'OK') + self.assertOnceInResponse(u'OK') def test_idle_then_noidle_enables_timeout(self): self.sendRequest(u'idle') @@ -93,39 +93,39 @@ class IdleHandlerTest(protocol.BaseTestCase): self.idleEvent(u'player') self.assertNoSubscriptions() self.assertNoEvents() - self.assertInResponse(u'changed: player') - self.assertInResponse(u'OK') + self.assertOnceInResponse(u'changed: player') + self.assertOnceInResponse(u'OK') def test_idle_player_then_event_player(self): self.sendRequest(u'idle player') self.idleEvent(u'player') self.assertNoSubscriptions() self.assertNoEvents() - self.assertInResponse(u'changed: player') - self.assertInResponse(u'OK') + self.assertOnceInResponse(u'changed: player') + self.assertOnceInResponse(u'OK') def test_idle_player_then_noidle(self): self.sendRequest(u'idle player') self.sendRequest(u'noidle') self.assertNoSubscriptions() self.assertNoEvents() - self.assertInResponse(u'OK') + self.assertOnceInResponse(u'OK') def test_idle_player_playlist_then_noidle(self): self.sendRequest(u'idle player playlist') self.sendRequest(u'noidle') self.assertNoEvents() self.assertNoSubscriptions() - self.assertInResponse(u'OK') + self.assertOnceInResponse(u'OK') def test_idle_player_playlist_then_player(self): self.sendRequest(u'idle player playlist') self.idleEvent(u'player') self.assertNoEvents() self.assertNoSubscriptions() - self.assertInResponse(u'changed: player') + self.assertOnceInResponse(u'changed: player') self.assertNotInResponse(u'changed: playlist') - self.assertInResponse(u'OK') + self.assertOnceInResponse(u'OK') def test_idle_playlist_then_player(self): self.sendRequest(u'idle playlist') @@ -141,8 +141,8 @@ class IdleHandlerTest(protocol.BaseTestCase): self.assertNoEvents() self.assertNoSubscriptions() self.assertNotInResponse(u'changed: player') - self.assertInResponse(u'changed: playlist') - self.assertInResponse(u'OK') + self.assertOnceInResponse(u'changed: playlist') + self.assertOnceInResponse(u'OK') def test_player(self): self.idleEvent(u'player') @@ -155,9 +155,9 @@ class IdleHandlerTest(protocol.BaseTestCase): self.sendRequest(u'idle player') self.assertNoEvents() self.assertNoSubscriptions() - self.assertInResponse(u'changed: player') + self.assertOnceInResponse(u'changed: player') self.assertNotInResponse(u'changed: playlist') - self.assertInResponse(u'OK') + self.assertOnceInResponse(u'OK') def test_player_then_playlist(self): self.idleEvent(u'player') @@ -171,8 +171,8 @@ class IdleHandlerTest(protocol.BaseTestCase): self.sendRequest(u'idle') self.assertNoEvents() self.assertNoSubscriptions() - self.assertInResponse(u'changed: player') - self.assertInResponse(u'OK') + self.assertOnceInResponse(u'changed: player') + self.assertOnceInResponse(u'OK') def test_player_then_playlist_then_idle(self): self.idleEvent(u'player') @@ -180,9 +180,9 @@ class IdleHandlerTest(protocol.BaseTestCase): self.sendRequest(u'idle') self.assertNoEvents() self.assertNoSubscriptions() - self.assertInResponse(u'changed: player') - self.assertInResponse(u'changed: playlist') - self.assertInResponse(u'OK') + self.assertOnceInResponse(u'changed: player') + self.assertOnceInResponse(u'changed: playlist') + self.assertOnceInResponse(u'OK') def test_player_then_idle_playlist(self): self.idleEvent(u'player') @@ -197,7 +197,7 @@ class IdleHandlerTest(protocol.BaseTestCase): self.sendRequest(u'noidle') self.assertNoEvents() self.assertNoSubscriptions() - self.assertInResponse(u'OK') + self.assertOnceInResponse(u'OK') def test_player_then_playlist_then_idle_playlist(self): self.idleEvent(u'player') @@ -206,5 +206,5 @@ class IdleHandlerTest(protocol.BaseTestCase): self.assertNoEvents() self.assertNoSubscriptions() self.assertNotInResponse(u'changed: player') - self.assertInResponse(u'changed: playlist') - self.assertInResponse(u'OK') + self.assertOnceInResponse(u'changed: playlist') + self.assertOnceInResponse(u'OK') From b33e66b0c807e532a20638415c05aaa322fd66c6 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sat, 23 Jul 2011 14:47:35 +0200 Subject: [PATCH 30/43] Add comment to idle filter --- mopidy/frontends/mpd/dispatcher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mopidy/frontends/mpd/dispatcher.py b/mopidy/frontends/mpd/dispatcher.py index 660f82ec..a638d6e9 100644 --- a/mopidy/frontends/mpd/dispatcher.py +++ b/mopidy/frontends/mpd/dispatcher.py @@ -135,7 +135,7 @@ class MpdDispatcher(object): return [] if not self._is_currently_idle() and self._is_noidle(request): - return [] + return [] # noidle was called before idle response = self._call_next_filter(request, response, filter_chain) From 9895f5197cfb1d910a130a2a4d692ea2a379919a Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sat, 23 Jul 2011 14:48:52 +0200 Subject: [PATCH 31/43] Test via on_receive instead of on_line_received to ensure timeout code is also tested --- tests/frontends/mpd/protocol/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/frontends/mpd/protocol/__init__.py b/tests/frontends/mpd/protocol/__init__.py index 00314946..8cd91d60 100644 --- a/tests/frontends/mpd/protocol/__init__.py +++ b/tests/frontends/mpd/protocol/__init__.py @@ -34,9 +34,10 @@ class BaseTestCase(unittest.TestCase): self.mixer.stop().get() settings.runtime.clear() - def sendRequest(self, request, clear=False): + def sendRequest(self, request): self.connection.response = [] - self.session.on_line_received(request) + request = '%s\n' % request.encode('utf-8') + self.session.on_receive({'received': request}) return self.connection.response def assertNoResponse(self): From 451b52fde543e2edd0a05b3dd569e67aab66be99 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 24 Jul 2011 01:59:32 +0200 Subject: [PATCH 32/43] Make sure we prevent timeouts when in idle mode --- mopidy/frontends/mpd/protocol/status.py | 4 ++-- mopidy/utils/network.py | 4 +++- tests/frontends/mpd/protocol/idle_test.py | 4 ---- tests/utils/network/lineprotocol_test.py | 12 ++++++++++++ 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/mopidy/frontends/mpd/protocol/status.py b/mopidy/frontends/mpd/protocol/status.py index 5a319a5d..5ac99dfe 100644 --- a/mopidy/frontends/mpd/protocol/status.py +++ b/mopidy/frontends/mpd/protocol/status.py @@ -82,7 +82,7 @@ def idle(context, subsystems=None): active = context.subscriptions.intersection(context.events) if not active: - context.session.connection.disable_timeout() + context.session.prevent_timeout = True return response = [] @@ -100,7 +100,7 @@ def noidle(context): return context.subscriptions = set() context.events = set() - context.session.connection.enable_timeout() + context.session.prevent_timeout = False @handle_request(r'^stats$') def stats(context): diff --git a/mopidy/utils/network.py b/mopidy/utils/network.py index b7cc144d..719def69 100644 --- a/mopidy/utils/network.py +++ b/mopidy/utils/network.py @@ -286,6 +286,7 @@ class LineProtocol(ThreadingActor): def __init__(self, connection): self.connection = connection + self.prevent_timeout = False self.recv_buffer = '' @property @@ -316,7 +317,8 @@ class LineProtocol(ThreadingActor): line = self.decode(line) self.on_line_received(line) - self.connection.enable_timeout() + if not self.prevent_timeout: + self.connection.enable_timeout() def on_stop(self): """Ensure that cleanup when actor stops.""" diff --git a/tests/frontends/mpd/protocol/idle_test.py b/tests/frontends/mpd/protocol/idle_test.py index 0f5d4b06..da16bf33 100644 --- a/tests/frontends/mpd/protocol/idle_test.py +++ b/tests/frontends/mpd/protocol/idle_test.py @@ -42,10 +42,6 @@ class IdleHandlerTest(protocol.BaseTestCase): self.assertNoEvents() self.assertNoResponse() - def test_noidle_does_not_call_enable_timeout(self): - self.sendRequest(u'noidle') - self.assertEqual(0, self.connection.enable_timeout.call_count) - def test_idle_player(self): self.sendRequest(u'idle player') self.assertEqualSubscriptions(['player']) diff --git a/tests/utils/network/lineprotocol_test.py b/tests/utils/network/lineprotocol_test.py index a87f461c..41d3fbf2 100644 --- a/tests/utils/network/lineprotocol_test.py +++ b/tests/utils/network/lineprotocol_test.py @@ -11,11 +11,13 @@ class LineProtocolTest(unittest.TestCase): self.mock = Mock(spec=network.LineProtocol) self.mock.terminator = network.LineProtocol.terminator self.mock.encoding = network.LineProtocol.encoding + self.mock.prevent_timeout = False def test_init_stores_values_in_attributes(self): network.LineProtocol.__init__(self.mock, sentinel.connection) self.assertEqual(sentinel.connection, self.mock.connection) self.assertEqual('', self.mock.recv_buffer) + self.assertFalse(self.mock.prevent_timeout) def test_on_receive_no_new_lines_adds_to_recv_buffer(self): self.mock.connection = Mock(spec=network.Connection) @@ -36,6 +38,16 @@ class LineProtocolTest(unittest.TestCase): self.mock.connection.disable_timeout.assert_called_once_with() self.mock.connection.enable_timeout.assert_called_once_with() + def test_on_receive_toggles_unless_prevent_timeout_is_set(self): + self.mock.connection = Mock(spec=network.Connection) + self.mock.recv_buffer = '' + self.mock.parse_lines.return_value = [] + self.mock.prevent_timeout = True + + network.LineProtocol.on_receive(self.mock, {'received': 'data'}) + self.mock.connection.disable_timeout.assert_called_once_with() + self.assertEqual(0, self.mock.connection.enable_timeout.call_count) + def test_on_receive_no_new_lines_calls_parse_lines(self): self.mock.connection = Mock(spec=network.Connection) self.mock.recv_buffer = '' From 6bd39187061432250a9fc78b11492e0fee1c9ef4 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 24 Jul 2011 12:03:30 +0200 Subject: [PATCH 33/43] Add idle addition to changelog --- docs/changes.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/changes.rst b/docs/changes.rst index 0a9ab925..19c65ee9 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -25,6 +25,9 @@ v0.6.0 (in development) - The local client now tries to lookup where your music is via XDG, it will fall-back to ``~/music`` or use whatever setting you set manually. +- The idle command is now supported by mopidy for the following subsystems: + player, playlist, options and mixer (Fixes: :issue:`32`). + **Changes** - Replace :attr:`mopidy.backends.base.Backend.uri_handlers` with From 2c0f2ab82cea30434b3793a918d96ce340c02b89 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 24 Jul 2011 19:48:41 +0200 Subject: [PATCH 34/43] Typo fix --- mopidy/frontends/mpd/dispatcher.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mopidy/frontends/mpd/dispatcher.py b/mopidy/frontends/mpd/dispatcher.py index a638d6e9..b502ee5c 100644 --- a/mopidy/frontends/mpd/dispatcher.py +++ b/mopidy/frontends/mpd/dispatcher.py @@ -129,8 +129,8 @@ class MpdDispatcher(object): def _idle_filter(self, request, response, filter_chain): if self._is_currently_idle() and not self._is_noidle(request): - logger.debug(u'Client send us %s, only %s is allowed while in ' - 'the idle state', repr(request), repr('noidle')) + logger.debug(u'Client sent us %s, only %s is allowed while in ' + 'the idle state', repr(request), repr(u'noidle')) self.context.session.close() return [] From 5d025a5721216c4f9ffb7c99665374e4a332b258 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 25 Jul 2011 00:30:21 +0200 Subject: [PATCH 35/43] Compile noidle regexp used for is_noidle check --- mopidy/frontends/mpd/dispatcher.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/mopidy/frontends/mpd/dispatcher.py b/mopidy/frontends/mpd/dispatcher.py index b502ee5c..cab014a8 100644 --- a/mopidy/frontends/mpd/dispatcher.py +++ b/mopidy/frontends/mpd/dispatcher.py @@ -27,6 +27,8 @@ class MpdDispatcher(object): back to the MPD session. """ + _noidle = re.compile(r'^noidle$') + def __init__(self, session=None): self.authenticated = False self.command_list = False @@ -128,13 +130,13 @@ class MpdDispatcher(object): ### Filter: idle def _idle_filter(self, request, response, filter_chain): - if self._is_currently_idle() and not self._is_noidle(request): + if self._is_currently_idle() and not self._noidle.match(request): logger.debug(u'Client sent us %s, only %s is allowed while in ' 'the idle state', repr(request), repr(u'noidle')) self.context.session.close() return [] - if not self._is_currently_idle() and self._is_noidle(request): + if not self._is_currently_idle() and self._noidle.match(request): return [] # noidle was called before idle response = self._call_next_filter(request, response, filter_chain) @@ -147,9 +149,6 @@ class MpdDispatcher(object): def _is_currently_idle(self): return bool(self.context.subscriptions) - def _is_noidle(self, request): - return re.match(r'^noidle$', request) - ### Filter: add OK From 4049b23c3f8baeb191c6657f42229dae3cc4c500 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 25 Jul 2011 00:50:41 +0200 Subject: [PATCH 36/43] Add concept of delimeter to complemend terminator in LineProtocol --- mopidy/utils/network.py | 13 +++++++++-- tests/utils/network/lineprotocol_test.py | 28 ++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/mopidy/utils/network.py b/mopidy/utils/network.py index b7cc144d..9a02035b 100644 --- a/mopidy/utils/network.py +++ b/mopidy/utils/network.py @@ -278,9 +278,13 @@ class LineProtocol(ThreadingActor): then splitting data along line boundaries. """ - #: What terminator to use to split lines. + #: Line terinator to use for outputed lines. terminator = '\n' + #: Regex to use for splitings lines, will be set compiled version of its + #: own value, or to `terminator`s value if it is not set itself. + delimeter = None + #: What encoding to expect incomming data to be in, can be :class:`None`. encoding = 'utf-8' @@ -288,6 +292,11 @@ class LineProtocol(ThreadingActor): self.connection = connection self.recv_buffer = '' + if self.delimeter: + self.delimeter = re.compile(self.delimeter) + else: + self.delimeter = re.compile(self.terminator) + @property def host(self): return self.connection.host @@ -325,7 +334,7 @@ class LineProtocol(ThreadingActor): def parse_lines(self): """Consume new data and yield any lines found.""" while re.search(self.terminator, self.recv_buffer): - line, self.recv_buffer = re.split(self.terminator, + line, self.recv_buffer = self.delimeter.split( self.recv_buffer, 1) yield line diff --git a/tests/utils/network/lineprotocol_test.py b/tests/utils/network/lineprotocol_test.py index a87f461c..57b78417 100644 --- a/tests/utils/network/lineprotocol_test.py +++ b/tests/utils/network/lineprotocol_test.py @@ -1,5 +1,6 @@ #encoding: utf-8 +import re import unittest from mopidy.utils import network @@ -11,11 +12,21 @@ class LineProtocolTest(unittest.TestCase): self.mock = Mock(spec=network.LineProtocol) self.mock.terminator = network.LineProtocol.terminator self.mock.encoding = network.LineProtocol.encoding + self.mock.delimeter = network.LineProtocol.delimeter def test_init_stores_values_in_attributes(self): + delimeter = re.compile(network.LineProtocol.terminator) network.LineProtocol.__init__(self.mock, sentinel.connection) self.assertEqual(sentinel.connection, self.mock.connection) self.assertEqual('', self.mock.recv_buffer) + self.assertEqual(delimeter, self.mock.delimeter) + + def test_init_compiles_delimeter(self): + self.mock.delimeter = '\r?\n' + delimeter = re.compile('\r?\n') + + network.LineProtocol.__init__(self.mock, sentinel.connection) + self.assertEqual(delimeter, self.mock.delimeter) def test_on_receive_no_new_lines_adds_to_recv_buffer(self): self.mock.connection = Mock(spec=network.Connection) @@ -74,18 +85,21 @@ class LineProtocolTest(unittest.TestCase): self.assertEqual(2, self.mock.on_line_received.call_count) def test_parse_lines_emtpy_buffer(self): + self.mock.delimeter = re.compile(r'\n') self.mock.recv_buffer = '' lines = network.LineProtocol.parse_lines(self.mock) self.assertRaises(StopIteration, lines.next) def test_parse_lines_no_terminator(self): + self.mock.delimeter = re.compile(r'\n') self.mock.recv_buffer = 'data' lines = network.LineProtocol.parse_lines(self.mock) self.assertRaises(StopIteration, lines.next) def test_parse_lines_termintor(self): + self.mock.delimeter = re.compile(r'\n') self.mock.recv_buffer = 'data\n' lines = network.LineProtocol.parse_lines(self.mock) @@ -93,7 +107,17 @@ class LineProtocolTest(unittest.TestCase): self.assertRaises(StopIteration, lines.next) self.assertEqual('', self.mock.recv_buffer) + def test_parse_lines_termintor_with_carriage_return(self): + self.mock.delimeter = re.compile(r'\r?\n') + self.mock.recv_buffer = 'data\r\n' + + lines = network.LineProtocol.parse_lines(self.mock) + self.assertEqual('data', lines.next()) + self.assertRaises(StopIteration, lines.next) + self.assertEqual('', self.mock.recv_buffer) + def test_parse_lines_no_data_before_terminator(self): + self.mock.delimeter = re.compile(r'\n') self.mock.recv_buffer = '\n' lines = network.LineProtocol.parse_lines(self.mock) @@ -102,6 +126,7 @@ class LineProtocolTest(unittest.TestCase): self.assertEqual('', self.mock.recv_buffer) def test_parse_lines_extra_data_after_terminator(self): + self.mock.delimeter = re.compile(r'\n') self.mock.recv_buffer = 'data1\ndata2' lines = network.LineProtocol.parse_lines(self.mock) @@ -110,6 +135,7 @@ class LineProtocolTest(unittest.TestCase): self.assertEqual('data2', self.mock.recv_buffer) def test_parse_lines_unicode(self): + self.mock.delimeter = re.compile(r'\n') self.mock.recv_buffer = u'æøå\n'.encode('utf-8') lines = network.LineProtocol.parse_lines(self.mock) @@ -118,6 +144,7 @@ class LineProtocolTest(unittest.TestCase): self.assertEqual('', self.mock.recv_buffer) def test_parse_lines_multiple_lines(self): + self.mock.delimeter = re.compile(r'\n') self.mock.recv_buffer = 'abc\ndef\nghi\njkl' lines = network.LineProtocol.parse_lines(self.mock) @@ -128,6 +155,7 @@ class LineProtocolTest(unittest.TestCase): self.assertEqual('jkl', self.mock.recv_buffer) def test_parse_lines_multiple_calls(self): + self.mock.delimeter = re.compile(r'\n') self.mock.recv_buffer = 'data1' lines = network.LineProtocol.parse_lines(self.mock) From 68c947ddf261c3db9632772c743bf9586d46f041 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 25 Jul 2011 00:52:32 +0200 Subject: [PATCH 37/43] Allow clients to use carriage return in mpd sessions --- mopidy/frontends/mpd/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mopidy/frontends/mpd/__init__.py b/mopidy/frontends/mpd/__init__.py index 8b6d3770..78742ed5 100644 --- a/mopidy/frontends/mpd/__init__.py +++ b/mopidy/frontends/mpd/__init__.py @@ -48,6 +48,7 @@ class MpdSession(network.LineProtocol): terminator = protocol.LINE_TERMINATOR encoding = protocol.ENCODING + delimeter = r'\r?\n' def __init__(self, connection): super(MpdSession, self).__init__(connection) From c724fcd7c9e12192e3c3f5afccadab6aede88f02 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 25 Jul 2011 01:00:18 +0200 Subject: [PATCH 38/43] Turns out idle and noidle are not commands that should be listed --- mopidy/frontends/mpd/protocol/reflection.py | 4 ++++ tests/frontends/mpd/protocol/reflection_test.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/mopidy/frontends/mpd/protocol/reflection.py b/mopidy/frontends/mpd/protocol/reflection.py index dbd76034..3618f5e1 100644 --- a/mopidy/frontends/mpd/protocol/reflection.py +++ b/mopidy/frontends/mpd/protocol/reflection.py @@ -27,6 +27,10 @@ def commands(context): command_names.remove('command_list_ok_begin') if 'command_list_end' in command_names: command_names.remove('command_list_end') + if 'idle' in command_names: + command_names.remove('idle') + if 'noidle' in command_names: + command_names.remove('noidle') if 'sticker' in command_names: command_names.remove('sticker') diff --git a/tests/frontends/mpd/protocol/reflection_test.py b/tests/frontends/mpd/protocol/reflection_test.py index 9cf1ef97..315e3051 100644 --- a/tests/frontends/mpd/protocol/reflection_test.py +++ b/tests/frontends/mpd/protocol/reflection_test.py @@ -9,14 +9,14 @@ class ReflectionHandlerTest(protocol.BaseTestCase): self.assertInResponse(u'command: commands') self.assertInResponse(u'command: play') self.assertInResponse(u'command: status') - self.assertInResponse(u'command: idle') - self.assertInResponse(u'command: noidle') # Check if commands you do not have access to are not present self.assertNotInResponse(u'command: kill') # Check if the blacklisted commands are not present self.assertNotInResponse(u'command: command_list_begin') self.assertNotInResponse(u'command: command_list_ok_begin') self.assertNotInResponse(u'command: command_list_end') + self.assertNotInResponse(u'command: idle') + self.assertNotInResponse(u'command: noidle') self.assertNotInResponse(u'command: sticker') self.assertInResponse(u'OK') From 01cfbead30b57803dd8dc58c9ec96bec13ba741c Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 25 Jul 2011 01:03:58 +0200 Subject: [PATCH 39/43] Use sets to compute output --- mopidy/frontends/mpd/protocol/reflection.py | 28 ++++++--------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/mopidy/frontends/mpd/protocol/reflection.py b/mopidy/frontends/mpd/protocol/reflection.py index 3618f5e1..df13b4b4 100644 --- a/mopidy/frontends/mpd/protocol/reflection.py +++ b/mopidy/frontends/mpd/protocol/reflection.py @@ -11,28 +11,16 @@ def commands(context): Shows which commands the current user has access to. """ if context.dispatcher.authenticated: - command_names = [command.name for command in mpd_commands] + command_names = set([command.name for command in mpd_commands]) else: - command_names = [command.name for command in mpd_commands - if not command.auth_required] + command_names = set([command.name for command in mpd_commands + if not command.auth_required]) - # No permission to use - if 'kill' in command_names: - command_names.remove('kill') - - # Not shown by MPD in its command list - if 'command_list_begin' in command_names: - command_names.remove('command_list_begin') - if 'command_list_ok_begin' in command_names: - command_names.remove('command_list_ok_begin') - if 'command_list_end' in command_names: - command_names.remove('command_list_end') - if 'idle' in command_names: - command_names.remove('idle') - if 'noidle' in command_names: - command_names.remove('noidle') - if 'sticker' in command_names: - command_names.remove('sticker') + # No one is permited to use kill, rest of commands are not listed by MPD, + # so we shouldn't either. + command_names = command_names - set(['kill', 'command_list_begin', + 'command_list_ok_begin', 'command_list_ok_begin', 'command_list_end', + 'idle', 'noidle', 'sticker']) return [('command', command_name) for command_name in sorted(command_names)] From 9fe4674b367b7714f235660d6d31019354db9b43 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 25 Jul 2011 01:24:19 +0200 Subject: [PATCH 40/43] Update on_received to handle that decode can fail --- mopidy/utils/network.py | 3 ++- tests/utils/network/lineprotocol_test.py | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/mopidy/utils/network.py b/mopidy/utils/network.py index 9a02035b..6b2f69e5 100644 --- a/mopidy/utils/network.py +++ b/mopidy/utils/network.py @@ -323,7 +323,8 @@ class LineProtocol(ThreadingActor): for line in self.parse_lines(): line = self.decode(line) - self.on_line_received(line) + if line is not None: + self.on_line_received(line) self.connection.enable_timeout() diff --git a/tests/utils/network/lineprotocol_test.py b/tests/utils/network/lineprotocol_test.py index 57b78417..f3877126 100644 --- a/tests/utils/network/lineprotocol_test.py +++ b/tests/utils/network/lineprotocol_test.py @@ -74,6 +74,15 @@ class LineProtocolTest(unittest.TestCase): network.LineProtocol.on_receive(self.mock, {'received': 'data\n'}) self.mock.on_line_received.assert_called_once_with(sentinel.decoded) + def test_on_receive_with_new_line_with_failed_decode(self): + self.mock.connection = Mock(spec=network.Connection) + self.mock.recv_buffer = '' + self.mock.parse_lines.return_value = [sentinel.line] + self.mock.decode.return_value = None + + network.LineProtocol.on_receive(self.mock, {'received': 'data\n'}) + self.assertEqual(0, self.mock.on_line_received.call_count) + def test_on_receive_with_new_lines_calls_on_recieve(self): self.mock.connection = Mock(spec=network.Connection) self.mock.recv_buffer = '' From b2188f13cbc36bf6286400621dec4a12a0ef23c9 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Mon, 25 Jul 2011 17:41:02 +0200 Subject: [PATCH 41/43] Typo fixes --- mopidy/utils/network.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mopidy/utils/network.py b/mopidy/utils/network.py index a1ddeb82..9306ccd7 100644 --- a/mopidy/utils/network.py +++ b/mopidy/utils/network.py @@ -278,11 +278,11 @@ class LineProtocol(ThreadingActor): then splitting data along line boundaries. """ - #: Line terinator to use for outputed lines. + #: Line terminator to use for outputed lines. terminator = '\n' - #: Regex to use for splitings lines, will be set compiled version of its - #: own value, or to `terminator`s value if it is not set itself. + #: Regex to use for spliting lines, will be set compiled version of its + #: own value, or to ``terminator``s value if it is not set itself. delimeter = None #: What encoding to expect incomming data to be in, can be :class:`None`. From 02713d0b5c291708e909a4abc3b6b65d4ab6a1db Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 26 Jul 2011 10:52:25 +0200 Subject: [PATCH 42/43] Work around high start time position in ncmpcpp and mpc --- mopidy/frontends/mpd/protocol/status.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/mopidy/frontends/mpd/protocol/status.py b/mopidy/frontends/mpd/protocol/status.py index 5ac99dfe..8fe83be5 100644 --- a/mopidy/frontends/mpd/protocol/status.py +++ b/mopidy/frontends/mpd/protocol/status.py @@ -246,7 +246,13 @@ def _status_time(futures): _status_time_total(futures) // 1000) def _status_time_elapsed(futures): - return futures['playback.time_position'].get() + time_position = futures['playback.time_position'].get() + if time_position < 1000: + # XXX ncmpcpp and mpc interpretes the elapsed time as seconds instead + # of milliseconds if the elapsed time is less than approx. 1000. + return 0 + else: + return time_position def _status_time_total(futures): current_cp_track = futures['playback.current_cp_track'].get() From 6e2bfcf3d54cbb32cf92ce9361ce23a5bb3ee10a Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 26 Jul 2011 10:57:20 +0200 Subject: [PATCH 43/43] Add test for previous commit --- tests/frontends/mpd/status_test.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/frontends/mpd/status_test.py b/tests/frontends/mpd/status_test.py index d277227a..a7eeeb5e 100644 --- a/tests/frontends/mpd/status_test.py +++ b/tests/frontends/mpd/status_test.py @@ -165,6 +165,13 @@ class StatusHandlerTest(unittest.TestCase): self.assert_('elapsed' in result) self.assertEqual(int(result['elapsed']), 59123) + def test_status_method_when_starting_playing_contains_elapsed_zero(self): + self.backend.playback.state = PAUSED + self.backend.playback.play_time_accumulated = 123 # Less than 1000ms + result = dict(status.status(self.context)) + self.assert_('elapsed' in result) + self.assertEqual(int(result['elapsed']), 0) # Zero + def test_status_method_when_playing_contains_bitrate(self): self.backend.current_playlist.append([Track(bitrate=320)]) self.backend.playback.play()