From 5da950f61be5780e145547235b0f2fbabe7bdaae Mon Sep 17 00:00:00 2001 From: maxkoryukov Date: Wed, 3 Feb 2016 06:30:09 +0500 Subject: [PATCH] * improved unitesting (compat level for python 2.6 and unittest) * preparation for SoftChroot (stubs and mocks) --- Headphones.py | 5 ++++- headphones/__init__.py | 14 +++++++++++-- headphones/albumart_test.py | 2 +- headphones/config.py | 3 ++- headphones/config_test.py | 28 ++++++-------------------- headphones/exceptions.py | 6 ++++++ headphones/softchroot.py | 14 +++++++++++++ headphones/softchroot_test.py | 38 +++++++++++++++++++++++++++++++++++ headphones/unittestcompat.py | 28 ++++++++++++++++++++++++++ headphones/webserve.py | 1 + 10 files changed, 112 insertions(+), 27 deletions(-) create mode 100644 headphones/softchroot.py create mode 100644 headphones/softchroot_test.py create mode 100644 headphones/unittestcompat.py diff --git a/Headphones.py b/Headphones.py index 0d31534e..dc956a43 100755 --- a/Headphones.py +++ b/Headphones.py @@ -152,7 +152,10 @@ def main(): headphones.DB_FILE = os.path.join(headphones.DATA_DIR, 'headphones.db') # Read config and start logging - headphones.initialize(config_file) + try: + headphones.initialize(config_file) + except headphones.exceptions.SoftChrootError as e: + raise SystemExit('FATAL ERROR') if headphones.DAEMON: headphones.daemonize() diff --git a/headphones/__init__.py b/headphones/__init__.py index eb48476d..553a3b80 100644 --- a/headphones/__init__.py +++ b/headphones/__init__.py @@ -29,7 +29,8 @@ from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.triggers.interval import IntervalTrigger from headphones import versioncheck, logger import headphones.config - +from headphones.softchroot import SoftChroot +import headphones.exceptions # (append new extras to the end) POSSIBLE_EXTRAS = [ @@ -74,6 +75,7 @@ started = False DATA_DIR = None CONFIG = None +SOFT_CHROOT = None DB_FILE = None @@ -92,11 +94,11 @@ MIRRORLIST = ["musicbrainz.org", "headphones", "custom"] UMASK = None - def initialize(config_file): with INIT_LOCK: global CONFIG + global SOFT_CHROOT global _INITIALIZED global CURRENT_VERSION global LATEST_VERSION @@ -136,6 +138,14 @@ def initialize(config_file): logger.initLogger(console=not QUIET, log_dir=CONFIG.LOG_DIR, verbose=VERBOSE) + if CONFIG.SOFT_CHROOT: + # soft chroot defined, lets try to initialize: + try: + SOFT_CHROOT = SoftChroot(str(CONFIG.SOFT_CHROOT)) + except exceptions.SoftChrootError as e: + logger.error("SoftChroot error: %s", e) + raise e + if not CONFIG.CACHE_DIR: # Put the cache dir in the data dir for now CONFIG.CACHE_DIR = os.path.join(DATA_DIR, 'cache') diff --git a/headphones/albumart_test.py b/headphones/albumart_test.py index bfe8e80f..f18b11e3 100644 --- a/headphones/albumart_test.py +++ b/headphones/albumart_test.py @@ -1,6 +1,6 @@ #import unittest #import mock -from unittest import TestCase +from headphones.unittestcompat import TestCase import headphones.albumart diff --git a/headphones/config.py b/headphones/config.py index cd64058d..75695517 100644 --- a/headphones/config.py +++ b/headphones/config.py @@ -236,6 +236,7 @@ _CONFIG_DEFINITIONS = { 'SAB_USERNAME': (str, 'SABnzbd', ''), 'SAMPLINGFREQUENCY': (int, 'General', 44100), 'SEARCH_INTERVAL': (int, 'General', 1440), + 'SOFT_CHROOT': (path, 'General', ''), 'SONGKICK_APIKEY': (str, 'Songkick', 'nd1We7dFW2RqxPw8'), 'SONGKICK_ENABLED': (int, 'Songkick', 1), 'SONGKICK_FILTER_ENABLED': (int, 'Songkick', 0), @@ -308,7 +309,7 @@ class Config(object): definition = _CONFIG_DEFINITIONS[key] if len(definition) == 3: definition_type, section, default = definition - else: + elif len(definition) == 4: definition_type, section, _, default = definition return key, definition_type, section, ini_key, default diff --git a/headphones/config_test.py b/headphones/config_test.py index f5511a2e..4af7f725 100644 --- a/headphones/config_test.py +++ b/headphones/config_test.py @@ -1,26 +1,17 @@ #import unittest -import sys import mock -from unittest import TestCase +from headphones.unittestcompat import TestCase from mock import MagicMock import headphones.config from headphones.config import path -def is26(): - if sys.version_info[0] == 2 and sys.version_info[1] == 6: - return True - return False - class ConfigPathTest(TestCase): def test_path(self): p = path('/tmp') - #fuckin python 2.6: - if not is26(): - self.assertIsInstance(p, path) - self.assertIsNotNone(p) - self.assertTrue(True) + self.assertIsInstance(p, path) + self.assertIsNotNone(p) def test_path_call(self): s = '/tmp' @@ -41,10 +32,7 @@ class ConfigPathTest(TestCase): def test_path_repr(self): s = '/tmp' p1 = path(s) - #fuckin python 2.6: - if not is26(): - self.assertIn('headphones.config.path', p1.__repr__()) - self.assertTrue(True) + self.assertIn('headphones.config.path', p1.__repr__()) # patch required, since Config works ower a @@ -62,9 +50,7 @@ class ConfigTest(TestCase): """Test creating headphones.Config""" cf = headphones.config.Config('/tmp/notexist') - #fuckin python 2.6: - if not is26(): - self.assertIsInstance(cf, headphones.config.Config) + self.assertIsInstance(cf, headphones.config.Config) self.assertTrue(True) def test_write(self, config_obj_fabric_mock): @@ -84,6 +70,4 @@ class ConfigTest(TestCase): general_opts_set = conf_mock['General'].__setitem__.call_args_list general_opts_set = map(lambda x: x[0][0], general_opts_set) - #fuckin python 2.6: - if not is26(): - self.assertIn('download_dir', general_opts_set, 'There is no download_dir in ConfigObj (submodule of Config)') + self.assertIn('download_dir', general_opts_set, 'There is no download_dir in ConfigObj (submodule of Config)') diff --git a/headphones/exceptions.py b/headphones/exceptions.py index 5d0ddf52..107535c5 100644 --- a/headphones/exceptions.py +++ b/headphones/exceptions.py @@ -24,3 +24,9 @@ class NewzbinAPIThrottled(HeadphonesException): """ Newzbin has throttled us, deal with it """ + +class SoftChrootError(HeadphonesException): + """ + Fatal errors in SoftChroot module + """ + pass diff --git a/headphones/softchroot.py b/headphones/softchroot.py new file mode 100644 index 00000000..6176be6c --- /dev/null +++ b/headphones/softchroot.py @@ -0,0 +1,14 @@ +import os +from headphones.exceptions import SoftChrootError + +class SoftChroot(object): + def __init__(self, path): + path = path.strip() + + if (not os.path.exists(path) or + not os.path.isdir(path)): + raise SoftChrootError('No such directory: %s' % path) + + path = path.strip(os.path.sep) + os.path.sep + + self.chroot = path diff --git a/headphones/softchroot_test.py b/headphones/softchroot_test.py new file mode 100644 index 00000000..847182c6 --- /dev/null +++ b/headphones/softchroot_test.py @@ -0,0 +1,38 @@ +import os +import mock +from headphones.unittestcompat import TestCase +from mock import MagicMock + +from headphones.softchroot import SoftChroot +from headphones.exceptions import SoftChrootError + +class SoftChrootTest(TestCase): + def test_create(self): + """ create headphones.SoftChroot """ + + cf = SoftChroot('/tmp/') + self.assertIsInstance(cf, SoftChroot) + + @mock.patch('headphones.config.ConfigObj', name='ConfigObjMock') + def test_create_on_file(self, config_obj_fabric_mock): + """ create SoftChroot on file, not a directory """ + + path = os.path.join('tmp', 'notexist', 'asdf', '11', '12', 'np', 'itsssss') + + with self.assertRaises(SoftChrootError) as exc: + cf = SoftChroot(path) + + self.assertRegexpMatches(str(exc.exception), r'No such directory') + self.assertRegexpMatches(str(exc.exception), path) + + @mock.patch('headphones.softchroot', name='SoftChrootMock') + def test_create_on_file(self, config_obj_fabric_mock): + """ create SoftChroot on file, not a directory """ + + path = os.path.join('tmp', 'notexist', 'asdf', '11', '12', 'np', 'itsssss') + + with self.assertRaises(SoftChrootError) as exc: + cf = SoftChroot(path) + + self.assertRegexpMatches(str(exc.exception), r'No such directory') + self.assertRegexpMatches(str(exc.exception), path) diff --git a/headphones/unittestcompat.py b/headphones/unittestcompat.py new file mode 100644 index 00000000..87427bd1 --- /dev/null +++ b/headphones/unittestcompat.py @@ -0,0 +1,28 @@ +import sys +from unittest import TestCase as TC + +def _is26(): + if sys.version_info[0] == 2 and sys.version_info[1] == 6: + return True + return False + +_dummy = _is26() + +def _d(f): + def decorate(self, *args, **kw): + if _dummy: + return self.assertTrue(True) + return f(self, *args, **kw) + return decorate + +class TestCase(TC): + """ + Wrapper for python 2.6 stubs + """ + @_d + def assertIsInstance(self, *args, **kw): + return super(TestCase, self).assertIsInstance(*args, **kw) + + @_d + def assertIsIn(self, *args, **kw): + return super(TestCase, self).assertIsIn(*args, **kw) diff --git a/headphones/webserve.py b/headphones/webserve.py index 561dea0e..0455cb92 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -1369,6 +1369,7 @@ class WebInterface(object): # Need to convert EXTRAS to a dictionary we can pass to the config: # it'll come in as a string like 2,5,6,8 + extra_munges = { "dj-mix": "dj_mix", "mixtape/street": "mixtape_street"