Resolves Issue #190

This commit is contained in:
BJ Dierkes 2013-10-23 18:45:35 -05:00
parent 89d4eeff2a
commit 727ac6be44
49 changed files with 492 additions and 28 deletions

4
.coveragerc Normal file
View File

@ -0,0 +1,4 @@
[report]
# These are problematic for coverage reporting
omit = cement/ext/ext_daemon.py

View File

@ -29,11 +29,12 @@ Bugs:
Features:
* :issue:`190` - Merge daemon extension into core
* :issue:`194` - Added pre_argument_parsing/post_argument_parsing hooks
* :issue:`196` - Added utils.misc.wrap
* :issue:`200` - Merge ext.mustache into mainline.
* :issue:`203` - Added support for external template directory
Incompatible:
* :issue:`173` - Deprecated 'has_key()' from configparser extension

View File

@ -23,6 +23,7 @@ def argument_validator(klass, obj):
# pylint: disable=W0105,W0232,W0232,R0903,E0213,R0923
class IArgument(interface.Interface):
"""
This class defines the Argument Handler Interface. Classes that
implement this handler must provide the methods and attributes defined
@ -41,6 +42,7 @@ class IArgument(interface.Interface):
"""
class IMeta:
"""Interface meta-data options."""
label = 'argument'
@ -101,9 +103,11 @@ class IArgument(interface.Interface):
# pylint: disable=W0105
class CementArgumentHandler(handler.CementBaseHandler):
"""Base class that all Argument Handlers should sub-class from."""
class Meta:
"""
Handler meta-data (can be passed as keyword arguments to the parent
class).

View File

@ -20,6 +20,7 @@ def cache_validator(klass, obj):
class ICache(interface.Interface):
"""
This class defines the Cache Handler Interface. Classes that
implement this handler must provide the methods and attributes defined
@ -42,6 +43,7 @@ class ICache(interface.Interface):
"""
# pylint: disable=W0232, C0111, R0903
class IMeta:
"""Interface meta-data."""
label = 'cache'
@ -109,11 +111,13 @@ class ICache(interface.Interface):
class CementCacheHandler(handler.CementBaseHandler):
"""
Base class that all Cache Handlers should sub-class from.
"""
class Meta:
"""
Handler meta-data (can be passed as keyword arguments to the parent
class).

View File

@ -21,6 +21,7 @@ def config_validator(klass, obj):
class IConfig(interface.Interface):
"""
This class defines the Config Handler Interface. Classes that
implement this handler must provide the methods and attributes defined
@ -51,6 +52,7 @@ class IConfig(interface.Interface):
"""
# pylint: disable=W0232, C0111, R0903
class IMeta:
"""Interface meta-data."""
label = 'config'
"""The string identifier of the interface."""
@ -169,11 +171,13 @@ class IConfig(interface.Interface):
class CementConfigHandler(handler.CementBaseHandler):
"""
Base class that all Config Handlers should sub-class from.
"""
class Meta:
"""
Handler meta-data (can be passed as keyword arguments to the parent
class).

View File

@ -51,6 +51,7 @@ def controller_validator(klass, obj):
class IController(interface.Interface):
"""
This class defines the Controller Handler Interface. Classes that
implement this handler must provide the methods and attributes defined
@ -71,6 +72,7 @@ class IController(interface.Interface):
"""
# pylint: disable=W0232, C0111, R0903
class IMeta:
"""Interface meta-data."""
label = 'controller'
@ -112,6 +114,7 @@ class IController(interface.Interface):
class expose(object):
"""
Used to expose controller functions to be listed as commands, and to
decorate the function with Meta data for the argument parser.
@ -143,6 +146,7 @@ class expose(object):
"""
# pylint: disable=W0622
def __init__(self, help='', hide=False, aliases=[]):
self.hide = hide
self.help = help
@ -163,6 +167,7 @@ class expose(object):
# pylint: disable=R0921
class CementBaseController(handler.CementBaseHandler):
"""
This is an implementation of the
`IControllerHandler <#cement.core.controller.IController>`_ interface, but
@ -198,6 +203,7 @@ class CementBaseController(handler.CementBaseHandler):
"""
class Meta:
"""
Controller meta-data (can be passed as keyword arguments to the parent
class).

View File

@ -2,12 +2,14 @@
class FrameworkError(Exception):
"""
General framework (non-application) related errors.
:param msg: The error message.
"""
def __init__(self, msg):
Exception.__init__(self)
self.msg = msg
@ -17,11 +19,13 @@ class FrameworkError(Exception):
class InterfaceError(FrameworkError):
"""Interface related errors."""
pass
class CaughtSignal(FrameworkError):
"""
Raised when a defined signal is caught. For more information regarding
signals, reference the
@ -31,6 +35,7 @@ class CaughtSignal(FrameworkError):
:param frame: The signal frame.
"""
def __init__(self, signum, frame):
msg = 'Caught signal %s' % signum
super(CaughtSignal, self).__init__(msg)

View File

@ -25,6 +25,7 @@ def extension_validator(klass, obj):
class IExtension(interface.Interface):
"""
This class defines the Extension Handler Interface. Classes that
implement this handler must provide the methods and attributes defined
@ -48,6 +49,7 @@ class IExtension(interface.Interface):
# pylint: disable=W0232, C0111, R0903
class IMeta:
"""Interface meta-data."""
label = 'extension'
@ -93,7 +95,9 @@ class IExtension(interface.Interface):
class CementExtensionHandler(handler.CementBaseHandler):
class Meta:
"""
Handler meta-data (can be passed as keyword arguments to the parent
class).

View File

@ -19,6 +19,7 @@ LOG = minimal_logger(__name__)
class NullOut(object):
def write(self, s):
pass
@ -45,6 +46,7 @@ def cement_signal_handler(signum, frame):
class CementApp(meta.MetaMixin):
"""
The primary class to build applications from.
@ -98,6 +100,7 @@ class CementApp(meta.MetaMixin):
"""
class Meta:
"""
Application meta-data (can also be passed as keyword arguments to the
parent class).
@ -543,10 +546,10 @@ class CementApp(meta.MetaMixin):
def get_last_rendered(self):
"""
DEPRECATION WARNING: This function is deprecated as of Cement 2.1.3
in favor of the `self.last_rendered` property, and will be removed in
in favor of the `self.last_rendered` property, and will be removed in
future versions of Cement.
Return the (data, output_text) tuple of the last time self.render()
Return the (data, output_text) tuple of the last time self.render()
was called.
:returns: tuple (data, output_text)
@ -587,7 +590,7 @@ class CementApp(meta.MetaMixin):
LOG.debug("laying cement for the '%s' application" %
self._meta.label)
### overrides via command line
# overrides via command line
suppress_output = False
if '--debug' in self._meta.argv:

View File

@ -11,9 +11,11 @@ LOG = minimal_logger(__name__)
class CementBaseHandler(meta.MetaMixin):
"""Base handler class that all Cement Handlers should subclass from."""
class Meta:
"""
Handler meta-data (can also be passed as keyword arguments to the
parent class).

View File

@ -9,22 +9,26 @@ DEFAULT_META = ['interface', 'label', 'config_defaults', 'config_section']
class Interface(object):
"""
An interface definition class. All Interfaces should subclass from
here. Note that this is not an implementation and should never be
used directly.
"""
def __init__(self):
raise exc.InterfaceError("Interfaces can not be used directly.")
class Attribute(object):
"""
An interface attribute definition.
:param description: The description of the attribute.
"""
def __init__(self, description):
self.description = description

View File

@ -24,6 +24,7 @@ def log_validator(klass, obj):
class ILog(interface.Interface):
"""
This class defines the Log Handler Interface. Classes that
implement this handler must provide the methods and attributes defined
@ -46,6 +47,7 @@ class ILog(interface.Interface):
"""
# pylint: disable=W0232, C0111, R0903
class IMeta:
"""Interface meta-data."""
label = 'log'
@ -122,11 +124,13 @@ class ILog(interface.Interface):
class CementLogHandler(handler.CementBaseHandler):
"""
Base class that all Log Handlers should sub-class from.
"""
class Meta:
"""
Handler meta-data (can be passed as keyword arguments to the parent
class).

View File

@ -2,6 +2,7 @@
class Meta(object):
"""
Model that acts as a container class for a meta attributes for a larger
class. It stuffs any kwarg it gets in it's init as an attribute of itself.
@ -17,6 +18,7 @@ class Meta(object):
class MetaMixin(object):
"""
Mixin that provides the Meta class support to add settings to instances
of slumber objects. Meta settings cannot start with a _.

View File

@ -21,6 +21,7 @@ def output_validator(klass, obj):
class IOutput(interface.Interface):
"""
This class defines the Output Handler Interface. Classes that
implement this handler must provide the methods and attributes defined
@ -43,6 +44,7 @@ class IOutput(interface.Interface):
"""
# pylint: disable=W0232, C0111, R0903
class IMeta:
"""Interface meta-data."""
label = 'output'
@ -76,11 +78,13 @@ class IOutput(interface.Interface):
class CementOutputHandler(handler.CementBaseHandler):
"""
Base class that all Output Handlers should sub-class from.
"""
class Meta:
"""
Handler meta-data (can be passed as keyword arguments to the parent
class).
@ -97,10 +101,12 @@ class CementOutputHandler(handler.CementBaseHandler):
class TemplateOutputHandler(CementOutputHandler):
"""
Base class for template base output handlers.
"""
def _load_template_from_file(self, template_path):
template_prefix = self.app._meta.template_dir.rstrip('/')
template_path = template_path.lstrip('/')

View File

@ -21,6 +21,7 @@ def plugin_validator(klass, obj):
class IPlugin(interface.Interface):
"""
This class defines the Plugin Handler Interface. Classes that
implement this handler must provide the methods and attributes defined
@ -86,12 +87,14 @@ class IPlugin(interface.Interface):
class CementPluginHandler(handler.CementBaseHandler):
"""
Base class that all Plugin Handlers should sub-class from.
"""
class Meta:
"""
Handler meta-data (can be passed as keyword arguments to the parent
class).

View File

@ -8,6 +8,7 @@ LOG = minimal_logger(__name__)
class ArgParseArgumentHandler(arg.CementArgumentHandler, ArgumentParser):
"""
This class implements the :ref:`IArgument <cement.core.arg>`
interface, and sub-classes from `argparse.ArgumentParser
@ -20,6 +21,7 @@ class ArgParseArgumentHandler(arg.CementArgumentHandler, ArgumentParser):
"""
class Meta:
"""Handler meta-data."""
interface = arg.IArgument

View File

@ -14,6 +14,7 @@ LOG = minimal_logger(__name__)
class ConfigParserConfigHandler(config.CementConfigHandler, RawConfigParser):
"""
This class is an implementation of the :ref:`IConfig <cement.core.config>`
interface. It handles configuration file parsing and the like by
@ -26,6 +27,7 @@ class ConfigParserConfigHandler(config.CementConfigHandler, RawConfigParser):
RawConfigParser on initialization.
"""
class Meta:
"""Handler meta-data."""
interface = config.IConfig

266
cement/ext/ext_daemon.py Normal file
View File

@ -0,0 +1,266 @@
"""Daemon Framework Extension"""
import os
import sys
import io
import pwd
import grp
from ..core import handler, hook, backend, exc
from ..utils.misc import minimal_logger
Log = minimal_logger(__name__)
Log = minimal_logger(__name__)
CEMENT_DAEMON_ENV = None
CEMENT_DAEMON_APP = None
class Environment(object):
"""
This class provides a mechanism for altering the running processes
environment.
Optional Arguments:
stdin
A file to read STDIN from. Default: /dev/null
stdout
A file to write STDOUT to. Default: /dev/null
stderr
A file to write STDERR to. Default: /dev/null
dir
The directory to run the process in.
pid_file
The filesystem path to where the PID (Process ID) should be
written to. Default: None
user
The user name to run the process as. Default: os.environ['USER']
group
The group name to run the process as. Default: The primary group
of os.environ['USER'].
umask
The umask to pass to os.umask(). Default: 0
"""
def __init__(self, **kw):
self.stdin = kw.get('stdin', '/dev/null')
self.stdout = kw.get('stdout', '/dev/null')
self.stderr = kw.get('stderr', '/dev/null')
self.dir = kw.get('dir', os.curdir)
self.pid_file = kw.get('pid_file', None)
self.umask = kw.get('umask', 0)
self.user = kw.get('user', os.environ['USER'])
# clean up
self.dir = os.path.abspath(os.path.expanduser(self.dir))
if self.pid_file:
self.pid_file = os.path.abspath(os.path.expanduser(self.pid_file))
try:
self.user = pwd.getpwnam(self.user)
except KeyError as e:
raise exc.FrameworkError("Daemon user '%s' doesn't exist." %
self.user)
try:
self.group = kw.get('group',
grp.getgrgid(self.user.pw_gid).gr_name)
self.group = grp.getgrnam(self.group)
except KeyError as e:
raise exc.FrameworkError("Daemon group '%s' doesn't exist." %
self.group)
def _write_pid_file(self):
"""
Writes os.getpid() out to self.pid_file.
"""
pid = str(os.getpid())
Log.debug('writing pid (%s) out to %s' % (pid, self.pid_file))
# setup pid
if self.pid_file:
f = open(self.pid_file, 'w')
f.write(pid)
f.close()
os.chown(os.path.dirname(self.pid_file),
self.user.pw_uid, self.group.gr_gid)
def switch(self):
"""
Switch the current process's user/group to self.user, and
self.group. Change directory to self.dir, and write the
current pid out to self.pid_file.
"""
# set the running uid/gid
Log.debug('setting process uid(%s) and gid(%s)' %
(self.user.pw_uid, self.group.gr_gid))
os.setgid(self.group.gr_gid)
os.setuid(self.user.pw_uid)
os.environ['HOME'] = self.user.pw_dir
os.chdir(self.dir)
if self.pid_file and os.path.exists(self.pid_file):
raise exc.FrameworkError("Process already running (%s)" %
self.pid_file)
else:
self._write_pid_file()
def daemonize(self):
"""
Fork the current process into a daemon.
References:
UNIX Programming FAQ
1.7 How do I get my program to act like a daemon?
http://www.unixguide.net/unix/programming/1.7.shtml
http://www.faqs.org/faqs/unix-faq/programmer/faq/
Advanced Programming in the Unix Environment
W. Richard Stevens, 1992, Addison-Wesley, ISBN 0-201-56317-7.
"""
Log.debug('attempting to daemonize the current process')
# Do first fork.
try:
pid = os.fork()
if pid > 0:
Log.debug('successfully detached from first parent')
os._exit(os.EX_OK)
except OSError as e:
sys.stderr.write("Fork #1 failed: (%d) %s\n" %
(e.errno, e.strerror))
sys.exit(1)
# Decouple from parent environment.
os.chdir(self.dir)
os.umask(int(self.umask))
os.setsid()
# Do second fork.
try:
pid = os.fork()
if pid > 0:
Log.debug('successfully detached from second parent')
os._exit(os.EX_OK)
except OSError as e:
sys.stderr.write("Fork #2 failed: (%d) %s\n" %
(e.errno, e.strerror))
sys.exit(1)
# Redirect standard file descriptors.
stdin = open(self.stdin, 'r')
stdout = open(self.stdout, 'a+')
stderr = open(self.stderr, 'a+')
if hasattr(sys.stdin, 'fileno'):
try:
os.dup2(stdin.fileno(), sys.stdin.fileno())
except io.UnsupportedOperation as e:
# FIXME: ?
pass
if hasattr(sys.stdout, 'fileno'):
try:
os.dup2(stdout.fileno(), sys.stdout.fileno())
except io.UnsupportedOperation as e:
# FIXME: ?
pass
if hasattr(sys.stderr, 'fileno'):
try:
os.dup2(stderr.fileno(), sys.stderr.fileno())
except io.UnsupportedOperation as e:
# FIXME: ?
pass
# Update our pid file
self._write_pid_file()
def daemonize():
"""
This function switches the running user/group to that configured in
config['daemon']['user'] and config['daemon']['group']. The default user
is os.environ['USER'] and the default group is that user's primary group.
A pid_file and directory to run in is also passed to the environment.
It is important to note that with the daemon extension enabled, the
environment will switch user/group/set pid/etc regardless of whether
the --daemon option was passed at command line or not. However, the
process will only 'daemonize' if the option is passed to do so. This
allows the program to run exactly the same in forground or background.
"""
# We want to honor the runtime user/group/etc even if --daemon is not
# passed... but only daemonize if it is.
global CEMENT_DAEMON_ENV
global CEMENT_DAEMON_APP
app = CEMENT_DAEMON_APP
CEMENT_DAEMON_ENV = Environment(
user=app.config.get('daemon', 'user'),
group=app.config.get('daemon', 'group'),
pid_file=app.config.get('daemon', 'pid_file'),
dir=app.config.get('daemon', 'dir'),
umask=app.config.get('daemon', 'umask'),
)
CEMENT_DAEMON_ENV.switch()
if '--daemon' in app.argv:
CEMENT_DAEMON_ENV.daemonize()
def extend_app(app):
"""
Adds the '--daemon' argument to the argument object, and sets the default
[daemon] config section options.
"""
global CEMENT_DAEMON_APP
CEMENT_DAEMON_APP = app
app.args.add_argument('--daemon', dest='daemon',
action='store_true', help='daemonize the process')
# Add default config
user = pwd.getpwnam(os.environ['USER'])
group = grp.getgrgid(user.pw_gid)
defaults = dict()
defaults['daemon'] = dict()
defaults['daemon']['user'] = user.pw_name
defaults['daemon']['group'] = group.gr_name
defaults['daemon']['pid_file'] = None
defaults['daemon']['dir'] = '/'
defaults['daemon']['umask'] = 0
app.config.merge(defaults, override=False)
app.extend('daemonize', daemonize)
def cleanup(app):
"""
After application run time, this hook just attempts to clean up the
pid_file if one was set, and exists.
"""
global CEMENT_DAEMON_ENV
if CEMENT_DAEMON_ENV and CEMENT_DAEMON_ENV.pid_file:
if os.path.exists(CEMENT_DAEMON_ENV.pid_file):
Log.debug('Cleaning up pid_file...')
pid = open(CEMENT_DAEMON_ENV.pid_file, 'r').read().strip()
# only remove it if we created it.
if int(pid) == int(os.getpid()):
os.remove(CEMENT_DAEMON_ENV.pid_file)
def load():
hook.register('post_setup', extend_app)
hook.register('pre_close', cleanup)

View File

@ -9,6 +9,7 @@ LOG = minimal_logger(__name__)
class JsonOutputHandler(output.CementOutputHandler):
"""
This class implements the :ref:`IOutput <cement.core.output>`
interface. It provides JSON output from a data dictionary using the
@ -22,6 +23,7 @@ class JsonOutputHandler(output.CementOutputHandler):
"""
class Meta:
"""Handler meta-data"""
interface = output.IOutput

View File

@ -11,6 +11,7 @@ try: # pragma: no cover
except AttributeError as e: # pragma: no cover
# Not supported on Python < 3.1/2.7 # pragma: no cover
class NullHandler(logging.Handler): # pragma: no cover
def handle(self, record): # pragma: no cover
pass # pragma: no cover
# pragma: no cover
@ -24,6 +25,7 @@ except AttributeError as e: # pragma: no cover
class LoggingLogHandler(log.CementLogHandler):
"""
This class is an implementation of the :ref:`ILog <cement.core.log>`
interface, and sets up the logging facility using the standard Python
@ -61,6 +63,7 @@ class LoggingLogHandler(log.CementLogHandler):
"""
class Meta:
"""Handler meta-data."""
interface = log.ILog

View File

@ -9,6 +9,7 @@ LOG = minimal_logger(__name__)
class MustacheOutputHandler(output.TemplateOutputHandler):
"""
This class implements the :ref:`IOutput <cement.core.output>`
interface. It provides text output from template and uses the

View File

@ -7,6 +7,7 @@ LOG = minimal_logger(__name__)
class NullOutputHandler(output.CementOutputHandler):
"""
This class is an internal implementation of the
:ref:`IOutput <cement.core.output>` interface. It does not take any
@ -14,6 +15,7 @@ class NullOutputHandler(output.CementOutputHandler):
"""
class Meta:
"""Handler meta-data"""
interface = output.IOutput

View File

@ -10,10 +10,11 @@ from ..utils.fs import abspath
LOG = minimal_logger(__name__)
### FIX ME: This is a redundant name... ?
# FIX ME: This is a redundant name... ?
class CementPluginHandler(plugin.CementPluginHandler):
"""
This class is an internal implementation of the
:ref:`IPlugin <cement.core.plugin>` interface. It does not take any
@ -22,6 +23,7 @@ class CementPluginHandler(plugin.CementPluginHandler):
"""
class Meta:
"""Handler meta-data."""
interface = plugin.IPlugin

View File

@ -4,13 +4,15 @@ import unittest
from ..core import backend, foundation
# shortcuts
from nose import SkipTest
from nose.tools import ok_ as ok
from nose.tools import eq_ as eq
from nose.tools import raises
from nose import SkipTest
from nose.plugins.attrib import attr
class TestApp(foundation.CementApp):
"""
Basic CementApp for generic testing.
@ -24,6 +26,7 @@ class TestApp(foundation.CementApp):
class CementTestCase(unittest.TestCase):
"""
A sub-class of unittest.TestCase.
@ -69,3 +72,15 @@ class CementTestCase(unittest.TestCase):
def eq(self, a, b, msg=None):
"""Shorthand for 'assert a == b, "%r != %r" % (a, b)'. """
return eq(a, b, msg)
# The following are for internal, Cement unit testing only
@attr('core')
class CementCoreTestCase(CementTestCase):
pass
@attr('ext')
class CementExtTestCase(CementTestCase):
pass

View File

@ -0,0 +1,10 @@
.. _cement.ext.ext_daemon:
:mod:`cement.ext.ext_daemon`
------------------------------
.. automodule:: cement.ext.ext_daemon
:members:
:undoc-members:
:private-members:
:show-inheritance:

View File

@ -48,6 +48,7 @@ Cement Extension Modules
ext/ext_argparse
ext/ext_configparser
ext/ext_daemon
ext/ext_json
ext/ext_logging
ext/ext_mustache

View File

@ -4,6 +4,7 @@ debug=0
detailed-errors=1
with-coverage=1
cover-package=cement
cover-inclusive=1
cover-erase=1
cover-html=1
cover-html-dir=coverage_report/

View File

@ -19,7 +19,8 @@ class MyCacheHandler(cache.CementCacheHandler):
def purge(self):
pass
class CacheTestCase(test.CementTestCase):
@test.attr('core')
class CacheTestCase(test.CementCoreTestCase):
def setUp(self):
super(CacheTestCase, self).setUp()
self.app = self.make_app(cache_handler=MyCacheHandler)

View File

@ -14,7 +14,7 @@ class BogusConfigHandler(config.CementConfigHandler):
class Meta:
label = 'bogus'
class ConfigTestCase(test.CementTestCase):
class ConfigTestCase(test.CementCoreTestCase):
@test.raises(exc.InterfaceError)
def test_invalid_config_handler(self):
handler.register(BogusConfigHandler)

View File

@ -87,7 +87,7 @@ class ArgumentConflict(controller.CementBaseController):
stacked_type = 'embedded'
arguments = [(['-f', '--foo'], dict())]
class ControllerTestCase(test.CementTestCase):
class ControllerTestCase(test.CementCoreTestCase):
def test_default(self):
app = self.make_app(base_controller=TestController)
app.setup()

View File

@ -3,7 +3,7 @@
from cement.core import exc
from cement.utils import test
class ExceptionTestCase(test.CementTestCase):
class ExceptionTestCase(test.CementCoreTestCase):
@test.raises(exc.FrameworkError)
def test_cement_runtime_error(self):
try:

View File

@ -12,7 +12,7 @@ class BogusExtensionHandler(extension.CementExtensionHandler):
interface = IBogus
label = 'bogus'
class ExtensionTestCase(test.CementTestCase):
class ExtensionTestCase(test.CementCoreTestCase):
@test.raises(exc.FrameworkError)
def test_invalid_extension_handler(self):
# the handler type bogus doesn't exist

View File

@ -40,7 +40,7 @@ def my_hook_two(app):
def my_hook_three(app):
return 3
class FoundationTestCase(test.CementTestCase):
class FoundationTestCase(test.CementCoreTestCase):
def setUp(self):
self.app = self.make_app('my_app')

View File

@ -50,7 +50,7 @@ class TestHandler(meta.MetaMixin):
interface = TestInterface
label = 'test'
class HandlerTestCase(test.CementTestCase):
class HandlerTestCase(test.CementCoreTestCase):
def setUp(self):
self.app = self.make_app()

View File

@ -19,7 +19,7 @@ def nosetests_hook(*args, **kw):
def cement_hook_five(app, data):
return data
class HookTestCase(test.CementTestCase):
class HookTestCase(test.CementCoreTestCase):
def setUp(self):
self.app = self.make_app()
hook.define('nosetests_hook')

View File

@ -20,7 +20,7 @@ class TestHandler2(handler.CementBaseHandler):
class TestHandler3():
pass
class InterfaceTestCase(test.CementTestCase):
class InterfaceTestCase(test.CementCoreTestCase):
def setUp(self):
self.app = self.make_app()

View File

@ -10,7 +10,7 @@ class BogusHandler1(log.CementLogHandler):
interface = log.ILog
label = 'bogus'
class LogTestCase(test.CementTestCase):
class LogTestCase(test.CementCoreTestCase):
def setUp(self):
self.app = self.make_app()

View File

@ -12,7 +12,7 @@ class TestMeta(meta.MetaMixin):
super(TestMeta, self).__init__(**kw)
self.option_three = kw.get('option_three', None)
class MetaTestCase(test.CementTestCase):
class MetaTestCase(test.CementCoreTestCase):
def test_passed_kwargs(self):
t = TestMeta(option_two='some other value', option_three='value three')
self.eq(t._meta.option_one, 'value one')

View File

@ -16,7 +16,7 @@ class TestOutputHandler(output.TemplateOutputHandler):
TEST_TEMPLATE = "%(foo)s"
class OutputTestCase(test.CementTestCase):
class OutputTestCase(test.CementCoreTestCase):
def setUp(self):
self.app = self.make_app()

View File

@ -54,7 +54,7 @@ def load():
"""
class PluginTestCase(test.CementTestCase):
class PluginTestCase(test.CementCoreTestCase):
def setUp(self):
self.app = self.make_app()

View File

@ -2,5 +2,6 @@
from cement.utils import test
from cement.utils.misc import init_defaults
class ConfigParserConfigHandlerTestCase(test.CementTestCase):
@test.attr('core')
class ConfigParserConfigHandlerTestCase(test.CementExtTestCase):
pass

98
tests/ext/daemon_tests.py Normal file
View File

@ -0,0 +1,98 @@
"""Tests for cement.ext.ext_daemon."""
import os
import tempfile
from random import random
from cement.core import handler, backend, log, hook, exc
from cement.utils import test
from cement.ext import ext_daemon
class DaemonExtTestCase(test.CementExtTestCase):
def setUp(self):
self.app = self.make_app()
def test_switch(self):
env = ext_daemon.Environment()
env.switch()
def test_switch_with_pid(self):
(_, tmpfile) = tempfile.mkstemp()
os.remove(tmpfile)
env = ext_daemon.Environment(pid_file=tmpfile)
env.switch()
try:
self.ok(os.path.exists(tmpfile))
finally:
os.remove(tmpfile)
@test.raises(exc.FrameworkError)
def test_pid_exists(self):
(_, tmpfile) = tempfile.mkstemp()
env = ext_daemon.Environment(pid_file=tmpfile)
env.switch()
try:
self.ok(os.path.exists(tmpfile))
except exc.FrameworkError as e:
self.ok(e.msg.startswith('Process already running'))
raise
finally:
env = ext_daemon.Environment()
env.switch()
os.remove(tmpfile)
@test.raises(exc.FrameworkError)
def test_bogus_user(self):
rand = random()
try:
env = ext_daemon.Environment(user='cement_test_user%s' % rand)
except exc.FrameworkError as e:
self.ok(e.msg.startswith('Daemon user'))
raise
finally:
env = ext_daemon.Environment()
env.switch()
@test.raises(exc.FrameworkError)
def test_bogus_group(self):
rand = random()
try:
env = ext_daemon.Environment(group='cement_test_group%s' % rand)
except exc.FrameworkError as e:
self.ok(e.msg.startswith('Daemon group'))
raise
finally:
env = ext_daemon.Environment()
env.switch()
def test_daemon(self):
(_, tmpfile) = tempfile.mkstemp()
os.remove(tmpfile)
app = self.make_app('test', argv=['--daemon'], extensions=['daemon'])
app.setup()
app.config.set('daemon', 'pid_file', tmpfile)
try:
### FIX ME: Can't daemonize, because nose loses sight of it
#app.daemonize()
app.run()
finally:
app.close()
ext_daemon.cleanup(app)
def test_daemon_not_passed(self):
app = self.make_app('myapp', extensions=['daemon'])
app.setup()
app.config.set('daemon', 'pid_file', None)
try:
app.run()
finally:
ext_daemon.cleanup(app)

View File

@ -5,7 +5,7 @@ import sys
from cement.core import handler, backend, hook
from cement.utils import test
class JsonExtTestCase(test.CementTestCase):
class JsonExtTestCase(test.CementExtTestCase):
def setUp(self):
self.app = self.make_app('tests',
extensions=['json'],

View File

@ -16,7 +16,8 @@ class MyLog(ext_logging.LoggingLogHandler):
def __init__(self, *args, **kw):
super(MyLog, self).__init__(*args, **kw)
class LoggingExtTestCase(test.CementTestCase):
@test.attr('core')
class LoggingExtTestCase(test.CementExtTestCase):
def test_alternate_namespaces(self):
defaults = init_defaults('myapp', 'log')
defaults['log']['to_console'] = False

View File

@ -7,7 +7,7 @@ from cement.core import exc, foundation, handler, backend, controller
from cement.utils import test
class MustacheExtTestCase(test.CementTestCase):
class MustacheExtTestCase(test.CementExtTestCase):
def setUp(self):
self.app = self.make_app('tests',
extensions=['mustache'],

View File

@ -4,7 +4,7 @@ import os
import tempfile
from cement.utils import fs, test
class FsUtilsTestCase(test.CementTestCase):
class FsUtilsTestCase(test.CementCoreTestCase):
def test_abspath(self):
path = fs.abspath('.')
self.ok(path.startswith('/'))

View File

@ -2,7 +2,7 @@
from cement.utils import test, misc
class BackendTestCase(test.CementTestCase):
class BackendTestCase(test.CementCoreTestCase):
def test_defaults(self):
defaults = misc.init_defaults('myapp', 'section2', 'section3')
defaults['myapp']['debug'] = True

View File

@ -6,7 +6,7 @@ from cement.utils import shell, test
def add(a, b):
return a + b
class ShellUtilsTestCase(test.CementTestCase):
class ShellUtilsTestCase(test.CementCoreTestCase):
def test_exec_cmd(self):
out, err, ret = shell.exec_cmd(['echo', 'KAPLA!'])
self.eq(ret, 0)

View File

@ -2,7 +2,7 @@
from cement.utils import version, test
class VersionUtilsTestCase(test.CementTestCase):
class VersionUtilsTestCase(test.CementCoreTestCase):
def test_get_version(self):
ver = version.get_version()
self.ok(ver.startswith('2.1'))