Merge branch 'develop' of github.com:mopidy/mopidy into develop

This commit is contained in:
Thomas Adamcik 2013-04-03 00:20:53 +02:00
commit de71154cb3
46 changed files with 217 additions and 214 deletions

View File

@ -99,7 +99,7 @@ installation using ``pip install Mopidy-Something==dev`` to work.
Before starting Mopidy, you must add your Soundspot username and password
to the Mopidy configuration file::
[ext.soundspot]
[soundspot]
username = alice
password = secret
@ -199,13 +199,13 @@ The default configuration for the extension is defined by the
``get_default_config()`` method in the ``Extension`` class which returns a
:mod:`ConfigParser` compatible config section. The config section's name should
be the same as the extension's short name, as defined in the ``entry_points``
part of ``setup.py``, but prefixed with ``ext.``, for example
``ext.soundspot``. All extensions should include an ``enabled`` config which
should default to ``true``. Provide good defaults for all config values so that
as few users as possible will need to change them. The exception is if the
config value has security implications; in that case you should default to the
most secure configuration. Leave any configurations that doesn't have
meaningful defaults blank, like ``username`` and ``password``.
part of ``setup.py``, for example ``soundspot``. All extensions should include
an ``enabled`` config which should default to ``true``. Provide good defaults
for all config values so that as few users as possible will need to change
them. The exception is if the config value has security implications; in that
case you should default to the most secure configuration. Leave any
configurations that doesn't have meaningful defaults blank, like ``username``
and ``password``.
::
@ -225,7 +225,7 @@ meaningful defaults blank, like ``username`` and ``password``.
__version__ = '0.1'
default_config = """
[ext.soundspot]
[soundspot]
enabled = true
username =
password =

View File

@ -1,7 +1,7 @@
from __future__ import unicode_literals
import codecs
import ConfigParser
import ConfigParser as configparser
import logging
import optparse
import os
@ -41,7 +41,8 @@ from mopidy.audio import Audio
from mopidy.config import default_config, config_schemas
from mopidy.core import Core
from mopidy.utils import (
deps, log, path, process, settings as settings_utils, versioning)
config as config_utils, deps, log, path, process,
settings as settings_utils, versioning)
logger = logging.getLogger('mopidy.main')
@ -53,17 +54,23 @@ def main():
loop = gobject.MainLoop()
options = parse_options()
config_files = options.config.split(':')
config_overrides = options.overrides
try:
log.setup_logging(options.verbosity_level, options.save_debug_log)
check_old_folders()
# TODO: we need a two stage logging setup as we want logging for
# extension loading and config loading.
log.setup_logging(None, options.verbosity_level, options.save_debug_log)
extensions = load_extensions()
load_config(options, extensions)
raw_config = load_config(config_files, config_overrides, extensions)
extensions = filter_enabled_extensions(raw_config, extensions)
config = validate_config(raw_config, extensions)
check_old_folders()
setup_settings(options.interactive)
audio = setup_audio()
backends = setup_backends(extensions, audio)
audio = setup_audio(config)
backends = setup_backends(config, extensions, audio)
core = setup_core(audio, backends)
setup_frontends(extensions, core)
setup_frontends(config, extensions, core)
loop.run()
except exceptions.SettingsError as ex:
logger.error(ex.message)
@ -80,9 +87,24 @@ def main():
process.stop_remaining_actors()
def check_config_override(option, opt, override):
try:
section, remainder = override.split('/', 1)
key, value = remainder.split('=', 1)
return (section, key, value)
except ValueError:
raise optparse.OptionValueError(
'option %s: must have the format section/key=value' % opt)
def parse_options():
parser = optparse.OptionParser(
version='Mopidy %s' % versioning.get_version())
# Ugly extension of optparse type checking magic :/
optparse.Option.TYPES += ('config_override',)
optparse.Option.TYPE_CHECKER['config_override'] = check_config_override
# NOTE First argument to add_option must be bytestrings on Python < 2.6.2
# See https://github.com/mopidy/mopidy/issues/302 for details
parser.add_option(
@ -106,22 +128,59 @@ def parse_options():
action='store_true', dest='save_debug_log',
help='save debug log to "./mopidy.log"')
parser.add_option(
b'--list-settings',
action='callback',
callback=settings_utils.list_settings_optparse_callback,
help='list current settings')
b'--show-config',
action='callback', callback=show_config_callback,
help='show current config')
parser.add_option(
b'--list-deps',
action='callback', callback=deps.list_deps_optparse_callback,
help='list dependencies and their versions')
parser.add_option(
b'--debug-thread',
action='store_true', dest='debug_thread',
help='run background thread that dumps tracebacks on SIGUSR1')
b'--config',
action='store', dest='config',
default='/etc/mopidy/mopidy.conf:$XDG_CONFIG_DIR/mopidy/mopidy.conf',
help='config files to use, colon seperated, later files override')
parser.add_option(
b'-o', b'--option',
action='append', dest='overrides', type='config_override',
help='`section/key=value` values to override config options')
return parser.parse_args(args=mopidy_args)[0]
def show_config_callback(option, opt, value, parser):
# TODO: don't use callback for this as --config or -o set after
# --show-config will be ignored.
files = getattr(parser.values, 'config', '').split(':')
overrides = getattr(parser.values, 'overrides', [])
extensions = load_extensions()
raw_config = load_config(files, overrides, extensions)
enabled_extensions = filter_enabled_extensions(raw_config, extensions)
config = validate_config(raw_config, enabled_extensions)
output = []
for section_name, schema in config_schemas.items():
options = config.get(section_name, {})
if not options:
continue
output.append(schema.format(section_name, options))
for extension in extensions:
if extension in enabled_extensions:
schema = extension.get_config_schema()
options = config.get(extension.ext_name, {})
output.append(schema.format(extension.ext_name, options))
else:
lines = ['[%s]' % extension.ext_name, 'enabled = false',
'# Config hidden as extension is disabled']
output.append('\n'.join(lines))
print '\n\n'.join(output)
sys.exit(0)
def check_old_folders():
# TODO: add old settings and pre extension storage locations?
old_settings_folder = os.path.expanduser('~/.mopidy')
if not os.path.isdir(old_settings_folder):
@ -136,9 +195,7 @@ def check_old_folders():
def load_extensions():
extensions = []
for entry_point in pkg_resources.iter_entry_points('mopidy.ext'):
logger.debug('Loading extension %s', entry_point.name)
# TODO Filter out disabled extensions
logger.debug('Loading entry point: %s', entry_point)
try:
extension_class = entry_point.load()
@ -150,6 +207,9 @@ def load_extensions():
extension = extension_class()
logger.debug(
'Loaded extension: %s %s', extension.dist_name, extension.version)
if entry_point.name != extension.ext_name:
logger.warning(
'Disabled extension %(ep)s: entry point name (%(ep)s) '
@ -157,8 +217,6 @@ def load_extensions():
{'ep': entry_point.name, 'ext': extension.ext_name})
continue
# TODO Validate configuration
try:
extension.validate_environment()
except exceptions.ExtensionError as ex:
@ -166,22 +224,39 @@ def load_extensions():
'Disabled extension %s: %s', entry_point.name, ex.message)
continue
logger.info(
'Loaded extension %s: %s %s',
entry_point.name, extension.dist_name, extension.version)
extensions.append(extension)
names = (e.ext_name for e in extensions)
logging.debug('Discovered extensions: %s', ', '.join(names))
return extensions
def load_config(options, extensions):
parser = ConfigParser.RawConfigParser()
def filter_enabled_extensions(raw_config, extensions):
boolean = config_utils.Boolean()
enabled_extensions = []
enabled_names = []
disabled_names = []
files = [
'/etc/mopidy/mopidy.conf',
'~/.config/mopidy/mopidy.conf',
]
# TODO Add config file given through `options` to `files`
# TODO Replace `files` with single file given through `options`
for extension in extensions:
# TODO: handle key and value errors.
enabled = raw_config[extension.ext_name]['enabled']
if boolean.deserialize(enabled):
enabled_extensions.append(extension)
enabled_names.append(extension.ext_name)
else:
disabled_names.append(extension.ext_name)
logging.info('Enabled extensions: %s', ', '.join(enabled_names))
logging.info('Disabled extensions: %s', ', '.join(disabled_names))
return enabled_extensions
def load_config(files, overrides, extensions):
parser = configparser.RawConfigParser()
files = [path.expand_path(f) for f in files]
sources = ['builtin-defaults'] + files + ['command-line']
logging.info('Loading config from: %s', ', '.join(sources))
# Read default core config
parser.readfp(StringIO.StringIO(default_config))
@ -192,7 +267,6 @@ def load_config(options, extensions):
# Load config from a series of config files
for filename in files:
filename = os.path.expanduser(filename)
try:
filehandle = codecs.open(filename, encoding='utf-8')
parser.readfp(filehandle)
@ -203,28 +277,41 @@ def load_config(options, extensions):
logger.error('Config file %s is not UTF-8 encoded', filename)
process.exit_process()
# TODO Merge config values given through `options` into `config`
raw_config = {}
for section in parser.sections():
raw_config[section] = dict(parser.items(section))
for section, key, value in overrides or []:
raw_config.setdefault(section, {})[key] = value
return raw_config
def validate_config(raw_config, extensions):
# Collect config schemas to validate against
sections_and_schemas = config_schemas.items()
for extension in extensions:
section_name = 'ext.%s' % extension.ext_name
if parser.getboolean(section_name, 'enabled'):
sections_and_schemas.append(
(section_name, extension.get_config_schema()))
sections_and_schemas.append(
(extension.ext_name, extension.get_config_schema()))
# Get validated config
config = {}
errors = {}
for section_name, schema in sections_and_schemas:
if not parser.has_section(section_name):
logger.error('Config section %s not found', section_name)
process.exit_process()
if section_name not in raw_config:
errors[section_name] = {section_name: 'section not found'}
try:
config[section_name] = schema.convert(parser.items(section_name))
items = raw_config[section_name].items()
config[section_name] = schema.convert(items)
except exceptions.ConfigError as error:
errors[section_name] = error
if errors:
for section_name, error in errors.items():
logger.error('[%s] config errors:', section_name)
for key in error:
logger.error('Config error: %s: %s', key, error[key])
process.exit_process()
logger.error('%s %s', key, error[key])
sys.exit(1)
return config
@ -240,9 +327,9 @@ def setup_settings(interactive):
sys.exit(1)
def setup_audio():
def setup_audio(config):
logger.info('Starting Mopidy audio')
return Audio.start().proxy()
return Audio.start(config=config).proxy()
def stop_audio():
@ -250,12 +337,12 @@ def stop_audio():
process.stop_actors_by_class(Audio)
def setup_backends(extensions, audio):
def setup_backends(config, extensions, audio):
logger.info('Starting Mopidy backends')
backends = []
for extension in extensions:
for backend_class in extension.get_backend_classes():
backend = backend_class.start(audio=audio).proxy()
backend = backend_class.start(config=config, audio=audio).proxy()
backends.append(backend)
return backends
@ -277,11 +364,11 @@ def stop_core():
process.stop_actors_by_class(Core)
def setup_frontends(extensions, core):
def setup_frontends(config, extensions, core):
logger.info('Starting Mopidy frontends')
for extension in extensions:
for frontend_class in extension.get_frontend_classes():
frontend_class.start(core=core)
frontend_class.start(config=config, core=core)
def stop_frontends(extensions):

View File

@ -38,7 +38,7 @@ class Audio(pykka.ThreadingActor):
#: The GStreamer state mapped to :class:`mopidy.audio.PlaybackState`
state = PlaybackState.STOPPED
def __init__(self):
def __init__(self, config):
super(Audio, self).__init__()
self._playbin = None

View File

@ -22,8 +22,12 @@ from mopidy.backends import base
from mopidy.models import Playlist, SearchResult
def create_dummy_backend_proxy(config=None, audio=None):
return DummyBackend.start(config=config, audio=audio).proxy()
class DummyBackend(pykka.ThreadingActor, base.Backend):
def __init__(self, audio):
def __init__(self, config, audio):
super(DummyBackend, self).__init__()
self.library = DummyLibraryProvider(backend=self)

View File

@ -6,7 +6,7 @@ from mopidy.utils import config, formatting
default_config = """
[ext.local]
[local]
# If the local extension should be enabled or not
enabled = true

View File

@ -13,7 +13,7 @@ logger = logging.getLogger('mopidy.backends.local')
class LocalBackend(pykka.ThreadingActor, base.Backend):
def __init__(self, audio):
def __init__(self, config, audio):
super(LocalBackend, self).__init__()
self.library = LocalLibraryProvider(backend=self)

View File

@ -7,7 +7,7 @@ from mopidy.utils import config, formatting
default_config = """
[ext.spotify]
[spotify]
# If the Spotify extension should be enabled or not
enabled = true

View File

@ -14,7 +14,7 @@ class SpotifyBackend(pykka.ThreadingActor, base.Backend):
# Imports inside methods are to prevent loading of __init__.py to fail on
# missing spotify dependencies.
def __init__(self, audio):
def __init__(self, config, audio):
super(SpotifyBackend, self).__init__()
from .library import SpotifyLibraryProvider

View File

@ -6,7 +6,7 @@ from mopidy.utils import config, formatting
default_config = """
[ext.stream]
[stream]
# If the stream extension should be enabled or not
enabled = true

View File

@ -13,7 +13,7 @@ logger = logging.getLogger('mopidy.backends.stream')
class StreamBackend(pykka.ThreadingActor, base.Backend):
def __init__(self, audio):
def __init__(self, config, audio):
super(StreamBackend, self).__init__()
self.library = StreamLibraryProvider(backend=self)

View File

@ -6,7 +6,7 @@ from mopidy.utils import config, formatting
default_config = """
[ext.http]
[http]
# If the HTTP extension should be enabled or not
enabled = true

View File

@ -23,7 +23,7 @@ logger = logging.getLogger('mopidy.frontends.http')
class HttpFrontend(pykka.ThreadingActor, CoreListener):
def __init__(self, core):
def __init__(self, config, core):
super(HttpFrontend, self).__init__()
self.core = core
self._setup_server()

View File

@ -6,7 +6,7 @@ from mopidy.utils import config, formatting
default_config = """
[ext.lastfm]
[lastfm]
# If the Last.fm extension should be enabled or not
enabled = true

View File

@ -20,7 +20,7 @@ API_SECRET = '94d9a09c0cd5be955c4afaeaffcaefcd'
class LastfmFrontend(pykka.ThreadingActor, CoreListener):
def __init__(self, core):
def __init__(self, config, core):
super(LastfmFrontend, self).__init__()
self.lastfm = None
self.last_start_time = None

View File

@ -6,7 +6,7 @@ from mopidy.utils import config, formatting
default_config = """
[ext.mpd]
[mpd]
# If the MPD extension should be enabled or not
enabled = true

View File

@ -14,7 +14,7 @@ logger = logging.getLogger('mopidy.frontends.mpd')
class MpdFrontend(pykka.ThreadingActor, CoreListener):
def __init__(self, core):
def __init__(self, config, core):
super(MpdFrontend, self).__init__()
hostname = network.format_hostname(settings.MPD_SERVER_HOSTNAME)
port = settings.MPD_SERVER_PORT

View File

@ -6,7 +6,7 @@ from mopidy.utils import formatting, config
default_config = """
[ext.mpris]
[mpris]
# If the MPRIS extension should be enabled or not
enabled = true

View File

@ -18,7 +18,7 @@ except ImportError as import_error:
class MprisFrontend(pykka.ThreadingActor, CoreListener):
def __init__(self, core):
def __init__(self, config, core):
super(MprisFrontend, self).__init__()
self.core = core
self.indicate_server = None

View File

@ -154,10 +154,10 @@ class List(ConfigValue):
values = re.split(r'\s*\n\s*', value.strip())
else:
values = re.split(r'\s*,\s*', value.strip())
return [v for v in values if v]
return tuple([v for v in values if v])
def serialize(self, value):
return '\n '.join(v.encode('utf-8') for v in value)
return '\n ' + '\n '.join(v.encode('utf-8') for v in value)
class LogLevel(ConfigValue):
@ -213,9 +213,9 @@ class ConfigSchema(object):
"""Logical group of config values that correspond to a config section.
Schemas are set up by assigning config keys with config values to
instances. Once setup :meth:`convert` can be called with a list of `(key,
value)` tuples to process. For convienience we also support :meth:`format`
method that can used for printing out the converted values.
instances. Once setup :meth:`convert` can be called with a list of
``(key, value)`` tuples to process. For convienience we also support
:meth:`format` method that can used for printing out the converted values.
"""
# TODO: Use collections.OrderedDict once 2.6 support is gone (#344)
def __init__(self):
@ -263,17 +263,12 @@ class ConfigSchema(object):
class ExtensionConfigSchema(ConfigSchema):
"""Sub-classed :class:`ConfigSchema` for use in extensions.
Ensures that `enabled` config value is present and that section name is
prefixed with ext.
Ensures that ``enabled`` config value is present.
"""
def __init__(self):
super(ExtensionConfigSchema, self).__init__()
self['enabled'] = Boolean()
def format(self, name, values):
return super(ExtensionConfigSchema, self).format(
'ext.%s' % name, values)
class LogLevelConfigSchema(object):
"""Special cased schema for handling a config section with loglevels.

View File

@ -7,7 +7,7 @@ from mopidy import settings
from . import deps, versioning
def setup_logging(verbosity_level, save_debug_log):
def setup_logging(config, verbosity_level, save_debug_log):
setup_root_logger()
setup_console_logging(verbosity_level)
if save_debug_log:

View File

@ -99,6 +99,7 @@ def split_path(path):
def expand_path(path):
# TODO: expandvars as well?
path = string.Template(path).safe_substitute(XDG_DIRS)
path = os.path.expanduser(path)
path = os.path.abspath(path)

View File

@ -184,42 +184,6 @@ def validate_settings(defaults, settings):
return errors
def list_settings_optparse_callback(*args):
"""
Prints a list of all settings.
Called by optparse when Mopidy is run with the :option:`--list-settings`
option.
"""
from mopidy import settings
print format_settings_list(settings)
sys.exit(0)
def format_settings_list(settings):
errors = settings.get_errors()
lines = []
for (key, value) in sorted(settings.current.iteritems()):
default_value = settings.default.get(key)
masked_value = mask_value_if_secret(key, value)
lines.append('%s: %s' % (
key, formatting.indent(pprint.pformat(masked_value), places=2)))
if value != default_value and default_value is not None:
lines.append(
' Default: %s' %
formatting.indent(pprint.pformat(default_value), places=4))
if errors.get(key) is not None:
lines.append(' Error: %s' % errors[key])
return '\n'.join(lines)
def mask_value_if_secret(key, value):
if key.endswith('PASSWORD') and value:
return '********'
else:
return value
def did_you_mean(setting, defaults):
"""Suggest most likely setting based on levenshtein."""
if not defaults:

View File

@ -17,7 +17,7 @@ class AudioTest(unittest.TestCase):
settings.MIXER = 'fakemixer track_max_volume=65536'
settings.OUTPUT = 'fakesink'
self.song_uri = path_to_uri(path_to_data_dir('song1.wav'))
self.audio = audio.Audio.start().proxy()
self.audio = audio.Audio.start(config=None).proxy()
def tearDown(self):
pykka.ActorRegistry.stop_all()
@ -60,7 +60,7 @@ class AudioTest(unittest.TestCase):
def test_set_volume_with_mixer_max_below_100(self):
settings.MIXER = 'fakemixer track_max_volume=40'
self.audio = audio.Audio.start().proxy()
self.audio = audio.Audio.start(config=None).proxy()
for value in range(0, 101):
self.assertTrue(self.audio.set_volume(value).get())
@ -81,7 +81,7 @@ class AudioTest(unittest.TestCase):
class AudioStateTest(unittest.TestCase):
def setUp(self):
self.audio = audio.Audio()
self.audio = audio.Audio(config=None)
def test_state_starts_as_stopped(self):
self.assertEqual(audio.PlaybackState.STOPPED, self.audio.state)

View File

@ -9,9 +9,12 @@ from mopidy.backends import listener
@mock.patch.object(listener.BackendListener, 'send')
class BackendEventsTest(object):
config = {}
def setUp(self):
self.audio = audio.DummyAudio.start().proxy()
self.backend = self.backend_class.start(audio=self.audio).proxy()
self.backend = self.backend_class.start(
config=self.config, audio=self.audio).proxy()
self.core = core.Core.start(backends=[self.backend]).proxy()
def tearDown(self):

View File

@ -23,9 +23,11 @@ class LibraryControllerTest(object):
uri='file://' + path_to_data_dir('uri2'), name='track2',
artists=artists[1:2], album=albums[1], date='2002', length=4000),
Track()]
config = {}
def setUp(self):
self.backend = self.backend_class.start(audio=None).proxy()
self.backend = self.backend_class.start(
config=self.config, audio=None).proxy()
self.core = core.Core(backends=[self.backend])
self.library = self.core.library

View File

@ -18,10 +18,12 @@ from tests.backends.base import populate_tracklist
class PlaybackControllerTest(object):
tracks = []
config = {}
def setUp(self):
self.audio = audio.DummyAudio.start().proxy()
self.backend = self.backend_class.start(audio=self.audio).proxy()
self.backend = self.backend_class.start(
config=self.config, audio=self.audio).proxy()
self.core = core.Core(backends=[self.backend])
self.playback = self.core.playback
self.tracklist = self.core.tracklist

View File

@ -13,13 +13,16 @@ from tests import unittest, path_to_data_dir
class PlaylistsControllerTest(object):
config = {}
def setUp(self):
settings.LOCAL_PLAYLIST_PATH = tempfile.mkdtemp()
settings.LOCAL_TAG_CACHE_FILE = path_to_data_dir('library_tag_cache')
settings.LOCAL_MUSIC_PATH = path_to_data_dir('')
self.audio = audio.DummyAudio.start().proxy()
self.backend = self.backend_class.start(audio=self.audio).proxy()
self.backend = self.backend_class.start(
config=self.config, audio=self.audio).proxy()
self.core = core.Core(backends=[self.backend])
def tearDown(self):

View File

@ -13,10 +13,12 @@ from tests.backends.base import populate_tracklist
class TracklistControllerTest(object):
tracks = []
config = {}
def setUp(self):
self.audio = audio.DummyAudio.start().proxy()
self.backend = self.backend_class.start(audio=self.audio).proxy()
self.backend = self.backend_class.start(
config=self.config, audio=self.audio).proxy()
self.core = core.Core(audio=self.audio, backends=[self.backend])
self.controller = self.core.tracklist
self.playback = self.core.playback

View File

@ -7,6 +7,7 @@ from tests.backends.base import events
class LocalBackendEventsTest(events.BackendEventsTest, unittest.TestCase):
backend_class = actor.LocalBackend
# TODO: setup config
def setUp(self):
settings.LOCAL_TAG_CACHE_FILE = path_to_data_dir('empty_tag_cache')

View File

@ -8,8 +8,8 @@ from tests.backends.base.library import LibraryControllerTest
class LocalLibraryControllerTest(LibraryControllerTest, unittest.TestCase):
backend_class = actor.LocalBackend
# TODO: setup config
def setUp(self):
settings.LOCAL_TAG_CACHE_FILE = path_to_data_dir('library_tag_cache')

View File

@ -15,6 +15,7 @@ class LocalPlaybackControllerTest(PlaybackControllerTest, unittest.TestCase):
backend_class = actor.LocalBackend
tracks = [
Track(uri=generate_song(i), length=4464) for i in range(1, 4)]
# TODO: setup config
def setUp(self):
settings.LOCAL_TAG_CACHE_FILE = path_to_data_dir('empty_tag_cache')

View File

@ -17,6 +17,7 @@ class LocalPlaylistsControllerTest(
PlaylistsControllerTest, unittest.TestCase):
backend_class = actor.LocalBackend
# TODO: setup config
def setUp(self):
settings.LOCAL_TAG_CACHE_FILE = path_to_data_dir('empty_tag_cache')
@ -96,7 +97,7 @@ class LocalPlaylistsControllerTest(
playlist = playlist.copy(tracks=[track])
playlist = self.core.playlists.save(playlist)
backend = self.backend_class(audio=self.audio)
backend = self.backend_class(config=self.config, audio=self.audio)
self.assert_(backend.playlists.playlists)
self.assertEqual(

View File

@ -13,6 +13,7 @@ class LocalTracklistControllerTest(TracklistControllerTest, unittest.TestCase):
backend_class = actor.LocalBackend
tracks = [
Track(uri=generate_song(i), length=4464) for i in range(1, 4)]
# TODO: setup config
def setUp(self):
settings.LOCAL_TAG_CACHE_FILE = path_to_data_dir('empty_tag_cache')

View File

@ -13,7 +13,7 @@ from tests import unittest
@mock.patch.object(core.CoreListener, 'send')
class BackendEventsTest(unittest.TestCase):
def setUp(self):
self.backend = dummy.DummyBackend.start(audio=None).proxy()
self.backend = dummy.create_dummy_backend_proxy()
self.core = core.Core.start(backends=[self.backend]).proxy()
def tearDown(self):

View File

@ -24,7 +24,7 @@ from tests import unittest
@mock.patch('cherrypy.engine.publish')
class HttpEventsTest(unittest.TestCase):
def setUp(self):
self.http = actor.HttpFrontend(core=mock.Mock())
self.http = actor.HttpFrontend(config=None, core=mock.Mock())
def test_track_playback_paused_is_broadcasted(self, publish):
publish.reset_mock()

View File

@ -13,7 +13,7 @@ from tests import unittest
class MpdDispatcherTest(unittest.TestCase):
def setUp(self):
self.backend = dummy.DummyBackend.start(audio=None).proxy()
self.backend = dummy.create_dummy_backend_proxy()
self.core = core.Core.start(backends=[self.backend]).proxy()
self.dispatcher = MpdDispatcher()

View File

@ -24,7 +24,7 @@ class MockConnection(mock.Mock):
class BaseTestCase(unittest.TestCase):
def setUp(self):
self.backend = dummy.DummyBackend.start(audio=None).proxy()
self.backend = dummy.create_dummy_backend_proxy()
self.core = core.Core.start(backends=[self.backend]).proxy()
self.connection = MockConnection()

View File

@ -22,7 +22,7 @@ STOPPED = PlaybackState.STOPPED
class StatusHandlerTest(unittest.TestCase):
def setUp(self):
self.backend = dummy.DummyBackend.start(audio=None).proxy()
self.backend = dummy.create_dummy_backend_proxy()
self.core = core.Core.start(backends=[self.backend]).proxy()
self.dispatcher = dispatcher.MpdDispatcher(core=self.core)
self.context = self.dispatcher.context

View File

@ -19,7 +19,7 @@ from tests import unittest
class BackendEventsTest(unittest.TestCase):
def setUp(self):
# As a plain class, not an actor:
self.mpris_frontend = actor.MprisFrontend(core=None)
self.mpris_frontend = actor.MprisFrontend(config=None, core=None)
self.mpris_object = mock.Mock(spec=objects.MprisObject)
self.mpris_frontend.mpris_object = self.mpris_object

View File

@ -26,7 +26,7 @@ STOPPED = PlaybackState.STOPPED
class PlayerInterfaceTest(unittest.TestCase):
def setUp(self):
objects.MprisObject._connect_to_dbus = mock.Mock()
self.backend = dummy.DummyBackend.start(audio=None).proxy()
self.backend = dummy.create_dummy_backend_proxy()
self.core = core.Core.start(backends=[self.backend]).proxy()
self.mpris = objects.MprisObject(core=self.core)

View File

@ -23,7 +23,7 @@ from tests import unittest
class PlayerInterfaceTest(unittest.TestCase):
def setUp(self):
objects.MprisObject._connect_to_dbus = mock.Mock()
self.backend = dummy.DummyBackend.start(audio=None).proxy()
self.backend = dummy.create_dummy_backend_proxy()
self.core = core.Core.start(backends=[self.backend]).proxy()
self.mpris = objects.MprisObject(core=self.core)

View File

@ -21,7 +21,7 @@ class RootInterfaceTest(unittest.TestCase):
def setUp(self):
objects.exit_process = mock.Mock()
objects.MprisObject._connect_to_dbus = mock.Mock()
self.backend = dummy.DummyBackend.start(audio=None).proxy()
self.backend = dummy.create_dummy_backend_proxy()
self.core = core.Core.start(backends=[self.backend]).proxy()
self.mpris = objects.MprisObject(core=self.core)

View File

@ -22,7 +22,9 @@ class HelpTest(unittest.TestCase):
self.assertIn('--quiet', output)
self.assertIn('--verbose', output)
self.assertIn('--save-debug-log', output)
self.assertIn('--list-settings', output)
self.assertIn('--show-config', output)
self.assertIn('--config', output)
self.assertIn('--option', output)
def test_help_gst_has_gstreamer_options(self):
mopidy_dir = os.path.dirname(mopidy.__file__)

View File

@ -199,10 +199,10 @@ class ListTest(unittest.TestCase):
def test_deserialize_conversion_success(self):
value = config.List()
expected = ['foo', 'bar', 'baz']
expected = ('foo', 'bar', 'baz')
self.assertEqual(expected, value.deserialize('foo, bar ,baz '))
expected = ['foo,bar', 'bar', 'baz']
expected = ('foo,bar', 'bar', 'baz')
self.assertEqual(expected, value.deserialize(' foo,bar\nbar\nbaz'))
def test_deserialize_enforces_required(self):
@ -212,12 +212,12 @@ class ListTest(unittest.TestCase):
def test_deserialize_respects_optional(self):
value = config.List(optional=True)
self.assertEqual([], value.deserialize(''))
self.assertEqual([], value.deserialize(' '))
self.assertEqual(tuple(), value.deserialize(''))
self.assertEqual(tuple(), value.deserialize(' '))
def test_serialize(self):
value = config.List()
result = value.serialize(['foo', 'bar', 'baz'])
result = value.serialize(('foo', 'bar', 'baz'))
self.assertRegexpMatches(result, r'foo\n\s*bar\n\s*baz')
@ -376,10 +376,6 @@ class ExtensionConfigSchemaTest(unittest.TestCase):
schema = config.ExtensionConfigSchema()
self.assertIsInstance(schema['enabled'], config.Boolean)
def test_section_name_is_prefixed(self):
schema = config.ExtensionConfigSchema()
self.assertEqual('[ext.foo]', schema.format('foo', {}))
class LogLevelConfigSchemaTest(unittest.TestCase):
def test_conversion(self):

View File

@ -38,7 +38,7 @@ class Calculator(object):
class JsonRpcTestBase(unittest.TestCase):
def setUp(self):
self.backend = dummy.DummyBackend.start(audio=None).proxy()
self.backend = dummy.create_dummy_backend_proxy()
self.core = core.Core.start(backends=[self.backend]).proxy()
self.jrw = jsonrpc.JsonRpcWrapper(

View File

@ -59,20 +59,6 @@ class ValidateSettingsTest(unittest.TestCase):
self.defaults, {'FOO': '', 'BAR': ''})
self.assertEqual(len(result), 2)
def test_masks_value_if_secret(self):
secret = setting_utils.mask_value_if_secret('SPOTIFY_PASSWORD', 'bar')
self.assertEqual('********', secret)
def test_does_not_mask_value_if_not_secret(self):
not_secret = setting_utils.mask_value_if_secret(
'SPOTIFY_USERNAME', 'foo')
self.assertEqual('foo', not_secret)
def test_does_not_mask_value_if_none(self):
not_secret = setting_utils.mask_value_if_secret(
'SPOTIFY_USERNAME', None)
self.assertEqual(None, not_secret)
class SettingsProxyTest(unittest.TestCase):
def setUp(self):
@ -179,54 +165,6 @@ class SettingsProxyTest(unittest.TestCase):
self.settings.validate(interactive=True)
class FormatSettingListTest(unittest.TestCase):
def setUp(self):
self.settings = setting_utils.SettingsProxy(settings)
def test_contains_the_setting_name(self):
self.settings.TEST = 'test'
result = setting_utils.format_settings_list(self.settings)
self.assertIn('TEST:', result, result)
def test_repr_of_a_string_value(self):
self.settings.TEST = 'test'
result = setting_utils.format_settings_list(self.settings)
self.assertIn("TEST: u'test'", result, result)
def test_repr_of_an_int_value(self):
self.settings.TEST = 123
result = setting_utils.format_settings_list(self.settings)
self.assertIn("TEST: 123", result, result)
def test_repr_of_a_tuple_value(self):
self.settings.TEST = (123, 'abc')
result = setting_utils.format_settings_list(self.settings)
self.assertIn("TEST: (123, u'abc')", result, result)
def test_passwords_are_masked(self):
self.settings.TEST_PASSWORD = 'secret'
result = setting_utils.format_settings_list(self.settings)
self.assertNotIn("TEST_PASSWORD: u'secret'", result, result)
self.assertIn("TEST_PASSWORD: u'********'", result, result)
def test_short_values_are_not_pretty_printed(self):
self.settings.FRONTEND = ('mopidy.frontends.mpd.MpdFrontend',)
result = setting_utils.format_settings_list(self.settings)
self.assertIn(
"FRONTEND: (u'mopidy.frontends.mpd.MpdFrontend',)", result)
def test_long_values_are_pretty_printed(self):
self.settings.FRONTEND = (
u'mopidy.frontends.mpd.MpdFrontend',
u'mopidy.frontends.lastfm.LastfmFrontend')
result = setting_utils.format_settings_list(self.settings)
self.assertIn(
"FRONTEND: \n"
" (u'mopidy.frontends.mpd.MpdFrontend',\n"
" u'mopidy.frontends.lastfm.LastfmFrontend')",
result)
class DidYouMeanTest(unittest.TestCase):
def testSuggestoins(self):
defaults = {