Added unit tests for new Config code.

This commit is contained in:
Paul J. Davis 2010-05-21 22:54:22 -04:00
parent f738d82bb7
commit 6ffbe54734
9 changed files with 288 additions and 22 deletions

View File

@ -0,0 +1,65 @@
#
# pylonstest - Pylons testing environment configuration
#
# The %(here)s variable will be replaced with the parent directory of this file
#
[DEFAULT]
debug = false
# Uncomment and replace with the address which should receive any error reports
#email_to = you@yourdomain.com
smtp_server = localhost
error_email_from = paste@localhost
[server:main]
use = egg:gunicorn#main
host = 192.168.0.1
port = 80
workers = 2
proc_name = brim
ignore_this = True
[app:main]
use = egg:pylonstest
full_stack = true
static_files = true
cache_dir = %(here)s/data
beaker.session.key = pylonstest
beaker.session.secret = somesecret
# Add additional test specific configuration options as necessary.
# Logging configuration
[loggers]
keys = root, routes, pylonstest
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = INFO
handlers = console
[logger_routes]
level = INFO
handlers =
qualname = routes.middleware
# "level = DEBUG" logs the route matched and routing variables.
[logger_pylonstest]
level = DEBUG
handlers =
qualname = pylonstest
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(asctime)s,%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

View File

@ -33,7 +33,13 @@ class Application(object):
# Load up the config file if its found.
if opts.config and os.path.exists(opts.config):
cfg = globals().copy()
cfg = {
"__builtins__": __builtins__,
"__name__": "__config__",
"__file__": opts.config,
"__doc__": None,
"__package__": None
}
try:
execfile(opts.config, cfg, cfg)
except Exception, e:
@ -42,6 +48,9 @@ class Application(object):
sys.exit(1)
for k, v in list(cfg.items()):
# Ignore unknown names
if k not in self.cfg.settings:
continue
self.cfg.set(k.lower(), v)
# Lastly, update the configuration with any command line

View File

@ -47,7 +47,12 @@ class PasterApplication(Application):
cfg['workers'] = int(lc.get('workers', 1))
cfg['umask'] = int(lc.get('umask', 0))
cfg['default_proc_name'] = gc.get('__file__')
cfg.update(dict((k,v) for (k,v) in lc.items() if k not in cfg))
for k, v in lc.items():
if k not in self.cfg.settings:
continue
cfg[k] = v
return cfg
def load(self):

View File

@ -5,7 +5,7 @@
from __future__ import with_statement
import ConfigParser
import copy
import grp
import inspect
import optparse
@ -23,7 +23,7 @@ KNOWN_SETTINGS = []
class Config(object):
def __init__(self, usage=None):
self.settings = dict((s.name, s) for s in KNOWN_SETTINGS)
self.settings = dict((s.name, s.copy()) for s in KNOWN_SETTINGS)
self.usage = usage
def __getattr__(self, name):
@ -78,7 +78,6 @@ class Config(object):
@property
def uid(self):
user = self.settings['user'].get()
if not user:
return os.geteuid()
elif user.isdigit() or isinstance(user, int):
@ -89,7 +88,6 @@ class Config(object):
@property
def gid(self):
group = self.settings['group'].get()
if not group:
return os.getegid()
elif group.isdigit() or isinstance(user, int):
@ -105,18 +103,6 @@ class Config(object):
else:
return self.settings['default_proc_name']
@property
def pre_fork(self):
return self.settings['pre_fork'].get()
@property
def post_fork(self):
return self.settings['post_fork'].get()
@property
def pre_exec(self):
return self.settings['pre_exec'].get()
class Setting(object):
def __init__(self, name):
self.name = name
@ -162,6 +148,9 @@ class Setting(object):
kwargs.pop("type")
parser.add_option(*args, **kwargs)
def copy(self):
return copy.copy(self)
def get(self):
return self.value
@ -172,6 +161,8 @@ class Setting(object):
def validate_bool(val):
if isinstance(val, types.BooleanType):
return val
if not isinstance(val, basestring):
raise TypeError("Invalid type for casting: %s" % val)
if val.lower().strip() == "true":
return True
elif val.lower().strip() == "false":
@ -182,11 +173,17 @@ def validate_bool(val):
def validate_pos_int(val):
if not isinstance(val, (types.IntType, types.LongType)):
val = int(val, 0)
else:
# Booleans are ints!
val = int(val)
print "Setting: %s" % val
if val < 0:
raise ValueError("Value must be positive: %s" % val)
return val
def validate_string(val):
if not isinstance(val, basestring):
raise TypeError("Not a string: %s" % val)
return val.strip()
def validate_callable(arity):
@ -203,7 +200,7 @@ with Setting("config") as s:
s.cli = ["-c", "--config"]
s.meta = "FILE"
s.validator = validate_string
s.default = "gunicorn.conf.py"
s.default = None
s.fmt_desc("""\
The path to a Gunicorn config file.

View File

@ -39,7 +39,7 @@ class TeeInput(object):
elif self._len and self._len < util.MAX_BODY:
self.tmp = StringIO()
else:
self.tmp = tempfile.TemporaryFile(dir=self.cfg['tmp_upload_dir'])
self.tmp = tempfile.TemporaryFile(dir=self.cfg.tmp_upload_dir)
if len(buf.getvalue()) > 0:
chunk, self.buf = parser.filter_body(buf)

View File

@ -167,7 +167,7 @@ def test_010(buf, p):
body = ""
while not p.body_eof():
chunk, buf2 = p.filter_body(buf2)
print chunk
#print chunk
if chunk:
body += chunk
t.eq(body, "all your base are belong to us")

186
tests/003-test-config.py Normal file
View File

@ -0,0 +1,186 @@
from nose.plugins.skip import SkipTest
import t
import functools
import os
import sys
from gunicorn import config
from gunicorn.app.base import Application
from gunicorn.workers.sync import SyncWorker
dirname = os.path.dirname(__file__)
def cfg_file():
return os.path.join(dirname, "config", "test_cfg.py")
def paster_ini():
return os.path.join(dirname, "..", "examples", "pylonstest", "nose.ini")
def PasterApp():
try:
from paste.deploy import loadapp, loadwsgi
except ImportError:
raise SkipTest()
from gunicorn.app.pasterapp import PasterApplication
return PasterApplication("no_usage")
class AltArgs(object):
def __init__(self, args=None):
self.args = args or []
self.orig = sys.argv
def __enter__(self):
sys.argv = self.args
def __exit__(self, exc_type, exc_inst, traceback):
sys.argv = self.orig
class NoConfigApp(Application):
def __init__(self):
super(NoConfigApp, self).__init__("no_usage")
def init(self, parser, opts, args):
pass
def load(self):
pass
def test_defaults():
c = config.Config()
for s in config.KNOWN_SETTINGS:
t.eq(s.default, c.settings[s.name].get())
def test_property_access():
c = config.Config()
for s in config.KNOWN_SETTINGS:
getattr(c, s.name)
# Class was loaded
t.eq(c.worker_class, SyncWorker)
# Debug affects workers
t.eq(c.workers, 1)
c.set("workers", 3)
t.eq(c.workers, 3)
c.set("debug", True)
t.eq(c.workers, 1)
# Address is parsed
t.eq(c.address, ("127.0.0.1", 8000))
# User and group defaults
t.eq(os.geteuid(), c.uid)
t.eq(os.getegid(), c.gid)
# Proc name
t.eq("gunicorn", c.proc_name)
# Not a config property
t.raises(AttributeError, getattr, c, "foo")
# Force to be not an error
class Baz(object):
def get(self):
return 3.14
c.settings["foo"] = Baz()
t.eq(c.foo, 3.14)
# Attempt to set a cfg not via c.set
t.raises(AttributeError, setattr, c, "proc_name", "baz")
# No setting for name
t.raises(AttributeError, c.set, "baz", "bar")
def test_bool_validation():
c = config.Config()
t.eq(c.debug, False)
c.set("debug", True)
t.eq(c.debug, True)
c.set("debug", "true")
t.eq(c.debug, True)
c.set("debug", "false")
t.eq(c.debug, False)
t.raises(ValueError, c.set, "debug", "zilch")
t.raises(TypeError, c.set, "debug", 4)
def test_pos_int_validation():
c = config.Config()
t.eq(c.workers, 1)
c.set("workers", 4)
t.eq(c.workers, 4)
c.set("workers", "5")
t.eq(c.workers, 5)
c.set("workers", "0xFF")
t.eq(c.workers, 255)
c.set("workers", True)
t.eq(c.workers, 1) # Yes. That's right...
t.raises(ValueError, c.set, "workers", -21)
t.raises(TypeError, c.set, "workers", c)
def test_str_validation():
c = config.Config()
t.eq(c.proc_name, "gunicorn")
c.set("proc_name", " foo ")
t.eq(c.proc_name, "foo")
t.raises(TypeError, c.set, "proc_name", 2)
def test_callable_validation():
c = config.Config()
def func(a, b):
pass
c.set("pre_fork", func)
t.eq(c.pre_fork, func)
t.raises(TypeError, c.set, "pre_fork", 1)
t.raises(TypeError, c.set, "pre_fork", lambda x: True)
def test_cmd_line():
with AltArgs(["prog_name", "-b", "blargh"]):
app = NoConfigApp()
t.eq(app.cfg.bind, "blargh")
with AltArgs(["prog_name", "-w", "3"]):
app = NoConfigApp()
t.eq(app.cfg.workers, 3)
with AltArgs(["prog_name", "-d"]):
app = NoConfigApp()
t.eq(app.cfg.debug, True)
def test_app_config():
with AltArgs():
app = NoConfigApp()
for s in config.KNOWN_SETTINGS:
t.eq(s.default, app.cfg.settings[s.name].get())
def test_load_config():
with AltArgs(["prog_name", "-c", cfg_file()]):
app = NoConfigApp()
t.eq(app.cfg.bind, "unix:/tmp/bar/baz")
t.eq(app.cfg.workers, 3)
t.eq(app.cfg.proc_name, "fooey")
def test_cli_overrides_config():
with AltArgs(["prog_name", "-c", cfg_file(), "-b", "blarney"]):
app = NoConfigApp()
t.eq(app.cfg.bind, "blarney")
t.eq(app.cfg.proc_name, "fooey")
def test_paster_config():
with AltArgs(["prog_name", paster_ini()]):
app = PasterApp()
t.eq(app.cfg.bind, "192.168.0.1:80")
t.eq(app.cfg.proc_name, "brim")
t.eq("ignore_me" in app.cfg.settings, False)
def test_cfg_over_paster():
with AltArgs(["prog_name", "-c", cfg_file(), paster_ini()]):
app = PasterApp()
t.eq(app.cfg.bind, "unix:/tmp/bar/baz")
t.eq(app.cfg.proc_name, "fooey")
t.eq(app.cfg.default_proc_name, "blurgh")
def test_cli_cfg_paster():
with AltArgs(["prog_name", "-c", cfg_file(), "-b", "whee", paster_ini()]):
app = PasterApp()
t.eq(app.cfg.bind, "whee")
t.eq(app.cfg.proc_name, "fooey")
t.eq(app.cfg.default_proc_name, "blurgh")

4
tests/config/test_cfg.py Normal file
View File

@ -0,0 +1,4 @@
bind = "unix:/tmp/bar/baz"
workers = 3
proc_name = "fooey"
default_proc_name = "blurgh"

View File

@ -75,7 +75,7 @@ class http_request(object):
def __call__(self, func):
def run():
fsock = FakeSocket(data_source(self.fname))
req = Request(fsock, ('127.0.0.1', 6000), ('127.0.0.1', 8000), Config({}))
req = Request(Config(), fsock, ('127.0.0.1', 6000), ('127.0.0.1', 8000))
func(req)
run.func_name = func.func_name
return run