Implemented a unit testing framework for Evennia. Unfortunately it seems it is only usable in latest Django SVN, due to a Django bug; Run "manage.py test-evennia" - if you get errors about SUPPORTS_TRANSACTIONS, you are affected by the bug. Since this is only likely to affect evennia devs at this point I added a few base tests in src/objects/tests.py as a template for those willing to help add unit tests.

This commit is contained in:
Griatch 2010-11-21 19:02:24 +00:00
parent a7899e0119
commit 502ebff1a2
7 changed files with 227 additions and 15 deletions

View file

@ -92,7 +92,7 @@ class CmdPassword(MuxCommand):
oldpass = self.lhslist[0] # this is already stripped by parse()
newpass = self.rhslist[0] # ''
try:
uaccount = caller.user
uaccount = caller.player.user
except AttributeError:
caller.msg("This is only applicable for players.")
return

View file

@ -11,11 +11,18 @@ import os
# Tack on the root evennia directory to the python path.
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
#------------------------------------------------------------
# Get Evennia version
#------------------------------------------------------------
try:
VERSION = open("%s%s%s" % (os.pardir, os.sep, 'VERSION')).readline().strip()
except IOError:
VERSION = "Unknown version"
#------------------------------------------------------------
# Check so session file exists in the current dir- if not, create it.
#------------------------------------------------------------
_CREATED_SETTINGS = False
if not os.path.exists('settings.py'):
# If settings.py doesn't already exist, create it and populate it with some
@ -88,7 +95,8 @@ from src.settings_default import *
###################################################
# Evennia components (django apps)
###################################################"""
###################################################
"""
settings_file.write(string)
settings_file.close()
@ -97,6 +105,9 @@ from src.settings_default import *
Welcome to Evennia (version %s)!
We created a fresh settings.py file for you.""" % VERSION
#------------------------------------------------------------
# Test the import of the settings file
#------------------------------------------------------------
try:
from game import settings
except Exception:
@ -114,10 +125,36 @@ except Exception:
print string
sys.exit(1)
# check required versions
#------------------------------------------------------------
# Test runner setup
#------------------------------------------------------------
os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings'
from django.test.simple import DjangoTestSuiteRunner
class EvenniaTestSuiteRunner(DjangoTestSuiteRunner):
"""
This test runner only runs tests on the apps specified in src/ and game/ to
avoid running the large number of tests defined by Django
"""
def build_suite(self, test_labels, extra_tests=None, **kwargs):
"""
Build a test suite for Evennia. test_labels is a list of apps to test.
If not given, a subset of settings.INSTALLED_APPS will be used.
"""
if not test_labels:
test_labels = [applabel.rsplit('.', 1)[1] for applabel in settings.INSTALLED_APPS
if (applabel.startswith('src.') or applabel.startswith('game.'))]
return super(EvenniaTestSuiteRunner, self).build_suite(test_labels, extra_tests=extra_tests, **kwargs)
def run_suite(self, test_labels=None, extra_tests=None, **kwargs):
"Run wrapper for the tests"
return super(EvenniaTestSuiteRunner, self).run_suite(self.build_suite(test_labels, extra_tests), **kwargs)
#------------------------------------------------------------
# This is run only if the module is called as a program
#------------------------------------------------------------
if __name__ == "__main__":
from django.core.management import execute_manager
# checks if the settings file was created this run
if _CREATED_SETTINGS:
print """
Edit your new settings.py file as needed, then run
@ -125,7 +162,15 @@ if __name__ == "__main__":
create the database and your superuser account.
"""
sys.exit()
# run the django setups
from src.utils.utils import check_evennia_dependencies
# running the unit tests
if len(sys.argv) > 1 and sys.argv[1] == 'test-evennia':
print "Running Evennia-specific test suites ..."
EvenniaTestSuiteRunner(sys.argv[2:]).run_suite()
sys.exit()
# run the standard django manager, if dependencies match
from src.utils.utils import check_evennia_dependencies
if check_evennia_dependencies():
from django.core.management import execute_manager
execute_manager(settings)

View file

@ -264,9 +264,15 @@ def format_multimatches(caller, matches):
# Main command-handler function
def cmdhandler(caller, raw_string, unloggedin=False):
def cmdhandler(caller, raw_string, unloggedin=False, testing=False):
"""
This is the main function to handle any string sent to the engine.
caller - calling object
raw_string - the command string given on the command line
unloggedin - if caller is an authenticated user or not
testing - if we should actually execute the command or not.
if True, the command instance will be returned instead.
"""
try: # catch bugs in cmdhandler itself
try: # catch special-type commands
@ -375,7 +381,11 @@ def cmdhandler(caller, raw_string, unloggedin=False):
# we make sure to validate its scripts.
cmd.obj.scripts.validate()
# Parse and execute
if testing:
# only return the command instance
return cmd
# Parse and execute
cmd.parse()
cmd.func()
# Done!
@ -395,6 +405,10 @@ def cmdhandler(caller, raw_string, unloggedin=False):
# cmd.obj is automatically made available.
# we make sure to validate its scripts.
cmd.obj.scripts.validate()
if testing:
# only return the command instance
return syscmd
# parse and run the command
syscmd.parse()

153
src/objects/tests.py Normal file
View file

@ -0,0 +1,153 @@
# -*- coding: utf-8 -*-
"""
Unit testing of the 'objects' Evennia component.
Runs as part of the Evennia's test suite with 'manage.py test-evennia'.
Please add new tests to this module as needed.
Guidelines:
A 'test case' is testing a specific component and is defined as a class inheriting from unittest.TestCase.
The test case class can have a method setUp() that creates and sets up the testing environment.
All methods inside the test case class whose names start with 'test' are used as test methods by the runner.
Inside the test methods, special member methods assert*() are used to test the behaviour.
"""
import re, time
try:
# this is a special optimized Django version, only available in current Django devel
from django.utils.unittest import TestCase
except ImportError:
# if our Django is older we use the normal version
# TODO: Switch this to django.test.TestCase when the but has been plugged that gives
# traceback when using that module over TransactionTestCase.
from django.test import TestCase
#from django.test import TransactionTestCase as TestCase
from django.conf import settings
from src.objects import models, objects
from src.utils import create
from src.server import session, sessionhandler
class TestObjAttrs(TestCase):
"""
Test aspects of ObjAttributes
"""
def setUp(self):
"set up the test"
self.attr = models.ObjAttribute()
self.obj1 = create.create_object(objects.Object, key="testobj1", location=None)
self.obj2 = create.create_object(objects.Object, key="testobj2", location=self.obj1)
# tests
def test_store_str(self):
hstring = "sdfv00=97sfjs842 ivfjlQKFos9GF^8dddsöäå-?%"
self.obj1.db.testattr = hstring
self.assertEqual(hstring, self.obj1.db.testattr)
def test_store_obj(self):
self.obj1.db.testattr = self.obj2
self.assertEqual(self.obj2 ,self.obj1.db.testattr)
self.assertEqual(self.obj2.location, self.obj1.db.testattr.location)
#------------------------------------------------------------
# Command testing
#------------------------------------------------------------
# print all feedback from test commands (can become very verbose!)
VERBOSE = False
class FakeSession(session.SessionProtocol):
"""
A fake session that implements dummy versions of the real thing; this is needed to mimic
a logged-in player.
"""
def connectionMade(self):
self.prep_session()
sessionhandler.add_session(self)
def prep_session(self):
self.server, self.address = None, "0.0.0.0"
self.name, self.uid = None, None
self.logged_in = False
self.encoding = "utf-8"
self.cmd_last, self.cmd_last_visible, self.cmd_conn_time = time.time(), time.time(), time.time()
self.cmd_total = 0
def disconnectClient(self):
pass
def lineReceived(self, raw_string):
pass
def msg(self, message, markup=True):
if VERBOSE:
print message
class TestCommand(TestCase):
"""
Sets up the basics of testing the default commands and the generic things
that should always be present in a command.
Inherit new tests from this.
"""
def setUp(self):
"sets up the testing environment"
self.room1 = create.create_object(settings.BASE_ROOM_TYPECLASS, key="room1")
self.room2 = create.create_object(settings.BASE_ROOM_TYPECLASS, key="room2")
# create a faux player/character for testing.
self.char1 = create.create_player("TestingPlayer", "testplayer@test.com", "testpassword", location=self.room1)
self.char1.player.user.is_superuser = True
sess = FakeSession()
sess.connectionMade()
sess.login(self.char1.player)
self.char2 = create.create_object(settings.BASE_CHARACTER_TYPECLASS, key="char2", location=self.room1)
self.obj1 = create.create_object(settings.BASE_OBJECT_TYPECLASS, key="obj1", location=self.room1)
self.obj2 = create.create_object(settings.BASE_OBJECT_TYPECLASS, key="obj2", location=self.room1)
self.exit1 = create.create_object(settings.BASE_EXIT_TYPECLASS, key="exit1", location=self.room1)
self.exit2 = create.create_object(settings.BASE_EXIT_TYPECLASS, key="exit2", location=self.room2)
def get_cmd(self, cmd_class, argument_string=""):
"""
Obtain a cmd instance from a class and an input string
Note: This does not make use of the cmdhandler functionality.
"""
cmd = cmd_class()
cmd.caller = self.char1
cmd.cmdstring = cmd_class.key
cmd.args = argument_string
cmd.cmdset = None
cmd.obj = self.char1
return cmd
def execute_cmd(self, raw_string):
"""
Creates the command through faking a normal command call;
This also mangles the input in various ways to test if the command
will be fooled.
"""
test1 = re.sub(r'\s', '', raw_string) # remove all whitespace inside it
test2 = "%s/åäö öäö;-:$£@*~^' 'test" % raw_string # inserting weird characters in call
test3 = "%s %s" % (raw_string, raw_string) # multiple calls
self.char1.execute_cmd(test1)
self.char1.execute_cmd(test2)
self.char1.execute_cmd(test3)
self.char1.execute_cmd(raw_string)
#------------------------------------------------------------
# Default set Command testing
#------------------------------------------------------------
class TestHome(TestCommand):
def test_call(self):
self.char1.home = self.room2
self.execute_cmd("home")
self.assertEqual(self.char1.location, self.room2)
class TestLook(TestCommand):
def test_call(self):
self.execute_cmd("look here")
class TestPassword(TestCommand):
def test_call(self):
self.execute_cmd("@password testpassword = newpassword")
class TestNick(TestCommand):
def test_call(self):
self.execute_cmd("nickname testalias = testaliasedstring")
self.assertEquals("testaliasedstring", self.char1.nicks.get("testalias", None))

View file

@ -538,7 +538,7 @@ def has_perm(accessing_obj, accessed_obj, lock_type, default_deny=False):
if typelist and lock_type in typelist]
if not locklist or not any(locklist):
# No locks; use default security policy
# No viable locks; use default security policy
return not default_deny
# we have locks of the right type. Set default flag OR on all that
@ -562,8 +562,7 @@ def has_perm(accessing_obj, accessed_obj, lock_type, default_deny=False):
# try to add permissions from connected player
if hasattr(accessing_obj, 'has_player') and accessing_obj.has_player:
# accessing_obj has a valid, connected player. We start with
# those permissions.
# accessing_obj has a valid, connected player. We start with those permissions.
player = accessing_obj.player
if player.is_superuser:
# superuser always has access

View file

@ -417,7 +417,7 @@ def create_player(name, email, password,
new_user = User.objects.create_user(name, email, password)
# create the associated Player for this User, and tie them together
new_player = PlayerDB(db_key=name, user=new_user)
new_player = PlayerDB(db_key=name, user=new_user, db_typeclass_path=typeclass)
new_player.save()
# assign mud permissions

View file

@ -133,7 +133,8 @@ def cemit_info(message):
infochan = Channel.objects.get_channel(infochan[0])
except Exception:
return
cname = infochan.key
cmessage = "\n".join(["[%s]: %s" % (cname, line) for line in message.split('\n')])
infochan.msg(cmessage)
if infochan:
cname = infochan.key
cmessage = "\n".join(["[%s]: %s" % (cname, line) for line in message.split('\n')])
infochan.msg(cmessage)