diff --git a/game/gamesrc/commands/basecommand.py b/game/gamesrc/commands/basecommand.py index e558292705..025c4b4433 100644 --- a/game/gamesrc/commands/basecommand.py +++ b/game/gamesrc/commands/basecommand.py @@ -1,17 +1,106 @@ """ -This is the parent class for all Commands in Evennia. Inherit from this and + + +Inherit from this and overload the member functions to define your own commands. -See commands/default/muxcommand.py for an example. +See src/commands/default/muxcommand.py for an example. """ from src.commands.command import Command as BaseCommand +from src.commands.default.muxcommand import MuxCommand as BaseMuxCommand from src.permissions import permissions from src.utils import utils +class MuxCommand(BaseMuxCommand): + """ + This sets up the basis for a Evennia's 'MUX-like' command + style. The idea is that most other Mux-related commands should + just inherit from this and don't have to implement much parsing of + their own unless they do something particularly advanced. + + Note that the class's __doc__ string (this text) is used by + Evennia to create the automatic help entry for the command, so + make sure to document consistently here. + + Most of the time your child classes should not need to implement + parse() at all, but only the main func() method for doing useful + things. See examples in src/commands/default. + + """ + + def parse(self): + """ + This method is called by the cmdhandler once the command name + has been identified. It creates a new set of member variables + that can be later accessed from self.func() (see below) + + The following variables are available for our use when entering this + method (from the command definition, and assigned on the fly by the + cmdhandler): + self.key - the name of this command ('look') + self.aliases - the aliases of this cmd ('l') + self.permissions - permission string for this command + self.help_category - overall category of command + + self.caller - the object calling this command + self.cmdstring - the actual command name used to call this + (this allows you to know which alias was used, + for example) + self.args - the raw input; everything following self.cmdstring. + self.cmdset - the cmdset from which this command was picked. Not + often used (useful for commands like 'help' or to + list all available commands etc) + self.obj - the object on which this command was defined. It is often + the same as self.caller. + + A MUX command has the following possible syntax: + + name[ with several words][/switch[/switch..]] arg1[,arg2,...] [[=|,] arg[,..]] + + The 'name[ with several words]' part is already dealt with by the + cmdhandler at this point, and stored in self.cmdname (we don't use + it here). The rest of the command is stored in self.args, which can start + with the switch indicator /. + + This parser breaks self.args into its constituents and stores them in the + following variables: + self.switches = [list of /switches (without the /)] + self.raw = This is the raw argument input, including switches + self.args = This is re-defined to be everything *except* the switches + self.lhs = Everything to the left of = (lhs:'left-hand side'). If + no = is found, this is identical to self.args. + self.rhs: Everything to the right of = (rhs:'right-hand side'). + If no '=' is found, this is None. + self.lhslist - [self.lhs split into a list by comma] + self.rhslist - [list of self.rhs split into a list by comma] + self.arglist = [list of space-separated args (stripped, including '=' if it exists)] + + All args and list members are stripped of excess whitespace around the + strings, but case is preserved. + """ + # parse all that makes it a MUX command (don't remove this) + super(MuxCommand, self).parse() + + def func(self): + """ + This is the hook function that actually does all the work. It is called + by the cmdhandler right after self.parser() finishes, and so has access + to all the variables defined therein. + """ + # this can be removed in your child class, it's just + # printing the ingoing variables as a demo + super(MuxCommand, self).func() + + + class Command(BaseCommand): """ + This is the basic command class (MuxCommand is a child of + this). Inherit from this if you want to create your own + command styles. + Note that the class's __doc__ string (this text) is used by Evennia to create the automatic help entry for the command, so make sure to document consistently here. @@ -22,7 +111,7 @@ class Command(BaseCommand): if srcobj is allowed to execute this command. This also determines if the command appears in help etc. - By default, We use checks of the 'c' type of lock to determine + By default, We use checks of the 'cmd' type of lock to determine if the command should be run. """ return permissions.has_perm(srcobj, self, 'cmd') @@ -61,18 +150,9 @@ class Command(BaseCommand): by the cmdhandler right after self.parser() finishes, and so has access to all the variables defined therein. """ - # a simple test command to show the available properties - string = "-" * 50 - string += "\n{w%s{n - Command variables from evennia:\n" % self.key - string += "-" * 50 - string += "\nname of cmd (self.key): {w%s{n\n" % self.key - string += "cmd aliases (self.aliases): {w%s{n\n" % self.aliases - string += "cmd perms (self.permissions): {w%s{n\n" % self.permissions - string += "help category (self.help_category): {w%s{n\n" % self.help_category - string += "object calling (self.caller): {w%s{n\n" % self.caller - string += "object storing cmdset (self.obj): {w%s{n\n" % self.obj - string += "command string given (self.cmdstring): {w%s{n\n" % self.cmdstring - # show cmdset.key instead of cmdset to shorten output - string += utils.fill("current cmdset (self.cmdset): {w%s{n\n" % self.cmdset) - - self.caller.msg(string) + + # this can be removed in your child class, it's just + # printing the ingoing variables as a demo + super(MuxCommand, self).func() + + diff --git a/game/gamesrc/commands/empty_cmdset.py b/game/gamesrc/commands/empty_cmdset.py new file mode 100644 index 0000000000..b525dff3df --- /dev/null +++ b/game/gamesrc/commands/empty_cmdset.py @@ -0,0 +1,65 @@ +""" +This is the second component of using a Command in your game - a +command set. A cmdset groups any number of commands together. CmdSets +are stored on character and objects and all available cmdsets are +searched when Evennia tries to determine if a command is +available. Sets can be merged and combined in many different ways (see +the docs). + +You can use the classes below as templates for extending the game with +new command sets; you can also import the default Evennia cmdset and +extend/overload that. + +To change default cmdset (the one all character start the game with), +Create your custom commands in other modules +(inheriting from game.gamesrc.commands.basecommand), add them to a +cmdset class, then set your settings.CMDSET_DEFAULT to point to this +new cmdset class. + +""" + +from src.commands.cmdset import CmdSet +from src.commands.default import cmdset_default + +from game.gamesrc.commands.basecommands import Command + +class ExampleCmdSet(CmdSet): + """ + Implements an example cmdset. + """ + + key = "ExampleSet" + + def at_cmdset_creation(self): + """ + This is the only method defined in a cmdset, called during + its creation. It should populate the set with command instances. + + Here we just add the base Command object. + """ + self.add(Command()) + + +class ExtendedDefaultSet(cmdset_default.DefaultCmdSet): + """ + This is an example of how to overload the default command + set defined in src/commands/default/cmdset_default.py. + + Here we copy everything by calling the parent, but you can + copy&paste any combination of the default command to customize + your default set. Next you change settings.CMDSET_DEFAULT to point + to this class. + """ + key = "DefaultMUX" + + def at_cmdset_creation(self): + """ + Populates the cmdset + """ + super(ExtendedDefaultSet, self).at_cmdset_creation() + + # + # any commands you add below will overload the default ones. + # + + diff --git a/src/commands/cmdset.py b/src/commands/cmdset.py index 53c3006b8f..44f269b634 100644 --- a/src/commands/cmdset.py +++ b/src/commands/cmdset.py @@ -204,13 +204,22 @@ class CmdSet(object): """ Add a command to this cmdset. - Note that if cmd already has + Note that if cmd already exists in set, + it will replace the old one (no priority checking etc + at this point; this is often used to overload + default commands). """ cmd = instantiate(cmd) + if cmd: if not hasattr(cmd, 'obj'): cmd.obj = self.cmdsetobj - self.commands.append(cmd) + try: + ic = self.commands.index(cmd) + self.commands[ic] = cmd # replace + except ValueError: + self.commands.append(cmd) + # extra run to make sure to avoid doublets self.commands = list(set(self.commands)) def remove(self, cmd): diff --git a/src/commands/command.py b/src/commands/command.py index b7f9d1ab09..53ddc3e520 100644 --- a/src/commands/command.py +++ b/src/commands/command.py @@ -145,5 +145,18 @@ class Command(object): of this module for which object properties are available (beyond those set in self.parse()) """ - string = "Command '%s' was executed with arg string '%s'." - self.caller.msg(string % (self.key, self.args)) + # a simple test command to show the available properties + string = "-" * 50 + string += "\n{w%s{n - Command variables from evennia:\n" % self.key + string += "-" * 50 + string += "\nname of cmd (self.key): {w%s{n\n" % self.key + string += "cmd aliases (self.aliases): {w%s{n\n" % self.aliases + string += "cmd perms (self.permissions): {w%s{n\n" % self.permissions + string += "help category (self.help_category): {w%s{n\n" % self.help_category + string += "object calling (self.caller): {w%s{n\n" % self.caller + string += "object storing cmdset (self.obj): {w%s{n\n" % self.obj + string += "command string given (self.cmdstring): {w%s{n\n" % self.cmdstring + # show cmdset.key instead of cmdset to shorten output + string += utils.fill("current cmdset (self.cmdset): {w%s{n\n" % self.cmdset) + + self.caller.msg(string) diff --git a/game/gamesrc/commands/default/__init__.py b/src/commands/default/__init__.py similarity index 100% rename from game/gamesrc/commands/default/__init__.py rename to src/commands/default/__init__.py diff --git a/game/gamesrc/commands/default/admin.py b/src/commands/default/admin.py similarity index 99% rename from game/gamesrc/commands/default/admin.py rename to src/commands/default/admin.py index 7c26492f63..bcde88fe6d 100644 --- a/game/gamesrc/commands/default/admin.py +++ b/src/commands/default/admin.py @@ -7,12 +7,11 @@ Admin commands from django.conf import settings from django.contrib.auth.models import User from src.players.models import PlayerDB -from game.gamesrc.commands.default.muxcommand import MuxCommand from src.server import sessionhandler from src.permissions.permissions import has_perm, has_perm_string from src.permissions.models import PermissionGroup from src.utils import utils - +from src.commands.default.muxcommand import MuxCommand class CmdBoot(MuxCommand): """ diff --git a/game/gamesrc/commands/default/batchprocess.py b/src/commands/default/batchprocess.py similarity index 99% rename from game/gamesrc/commands/default/batchprocess.py rename to src/commands/default/batchprocess.py index 749eb4d7ae..8a75b3132e 100644 --- a/game/gamesrc/commands/default/batchprocess.py +++ b/src/commands/default/batchprocess.py @@ -24,9 +24,8 @@ Example batch-code file: game/gamesrc/commands/examples/example_batch_code.py from traceback import format_exc from django.conf import settings from src.utils.batchprocessors import BATCHCMD, BATCHCODE -from game.gamesrc.commands.default.muxcommand import MuxCommand from src.commands.cmdset import CmdSet - +from src.commands.default.muxcommand import MuxCommand HEADER_WIDTH = 70 UTF8_ERROR = \ diff --git a/game/gamesrc/commands/default/building.py b/src/commands/default/building.py similarity index 99% rename from game/gamesrc/commands/default/building.py rename to src/commands/default/building.py index 4d7076453f..61a10d312f 100644 --- a/game/gamesrc/commands/default/building.py +++ b/src/commands/default/building.py @@ -7,8 +7,8 @@ Building and world design commands from django.conf import settings from src.permissions.permissions import has_perm, has_perm_string from src.objects.models import ObjectDB, ObjAttribute -from game.gamesrc.commands.default.muxcommand import MuxCommand from src.utils import create, utils, debug +from src.commands.default.muxcommand import MuxCommand class ObjManipCommand(MuxCommand): """ diff --git a/game/gamesrc/commands/default/cmdset_default.py b/src/commands/default/cmdset_default.py similarity index 90% rename from game/gamesrc/commands/default/cmdset_default.py rename to src/commands/default/cmdset_default.py index 929960b1df..ca68a75d2f 100644 --- a/game/gamesrc/commands/default/cmdset_default.py +++ b/src/commands/default/cmdset_default.py @@ -2,9 +2,9 @@ This module ties together all the commands of the default command set. """ from src.commands.cmdset import CmdSet -from game.gamesrc.commands.default import general, help, admin, system -from game.gamesrc.commands.default import tests, comms, building -from game.gamesrc.commands.default import batchprocess +from src.commands.default import general, help, admin, system +from src.commands.default import utils, comms, building +from src.commands.default import batchprocess class DefaultCmdSet(CmdSet): """ @@ -92,7 +92,7 @@ class DefaultCmdSet(CmdSet): self.add(batchprocess.CmdBatchCommands()) self.add(batchprocess.CmdBatchCode()) - # Testing commands - self.add(tests.CmdTest()) - self.add(tests.CmdTestPerms()) - self.add(tests.TestCom()) + # Testing/Utility commands + self.add(utils.CmdTest()) + self.add(utils.CmdTestPerms()) + self.add(utils.TestCom()) diff --git a/game/gamesrc/commands/default/cmdset_unloggedin.py b/src/commands/default/cmdset_unloggedin.py similarity index 91% rename from game/gamesrc/commands/default/cmdset_unloggedin.py rename to src/commands/default/cmdset_unloggedin.py index 2cf714a671..0a128da33c 100644 --- a/game/gamesrc/commands/default/cmdset_unloggedin.py +++ b/src/commands/default/cmdset_unloggedin.py @@ -4,7 +4,7 @@ The setting STATE_UNLOGGED should be set to the python path of the state instance in this module. """ from src.commands.cmdset import CmdSet -from game.gamesrc.commands.default import unloggedin +from src.commands.default import unloggedin class UnloggedinCmdSet(CmdSet): """ diff --git a/game/gamesrc/commands/default/comms.py b/src/commands/default/comms.py similarity index 99% rename from game/gamesrc/commands/default/comms.py rename to src/commands/default/comms.py index 82f3b4034b..9a1ee943b7 100644 --- a/game/gamesrc/commands/default/comms.py +++ b/src/commands/default/comms.py @@ -3,10 +3,9 @@ Comsys command module. """ from src.comms.models import Channel, Msg, ChannelConnection -from game.gamesrc.commands.default.muxcommand import MuxCommand from src.utils import create, utils from src.permissions.permissions import has_perm - +from src.commands.default.muxcommand import MuxCommand def find_channel(caller, channelname): """ diff --git a/game/gamesrc/commands/default/general.py b/src/commands/default/general.py similarity index 99% rename from game/gamesrc/commands/default/general.py rename to src/commands/default/general.py index c562caa6af..712380d2ce 100644 --- a/game/gamesrc/commands/default/general.py +++ b/src/commands/default/general.py @@ -9,8 +9,7 @@ from src.permissions.models import PermissionGroup from src.permissions.permissions import has_perm, has_perm_string from src.objects.models import HANDLE_SEARCH_ERRORS from src.utils import utils - -from game.gamesrc.commands.default.muxcommand import MuxCommand +from src.commands.default.muxcommand import MuxCommand class CmdHome(MuxCommand): """ diff --git a/game/gamesrc/commands/default/help.py b/src/commands/default/help.py similarity index 99% rename from game/gamesrc/commands/default/help.py rename to src/commands/default/help.py index d536376273..3ea366c885 100644 --- a/game/gamesrc/commands/default/help.py +++ b/src/commands/default/help.py @@ -11,7 +11,7 @@ from src.commands.command import Command from src.help.models import HelpEntry from src.permissions.permissions import has_perm from src.utils import create -from game.gamesrc.commands.default.muxcommand import MuxCommand +from src.commands.default.muxcommand import MuxCommand LIST_ARGS = ["list", "all"] diff --git a/game/gamesrc/commands/default/muxcommand.py b/src/commands/default/muxcommand.py similarity index 99% rename from game/gamesrc/commands/default/muxcommand.py rename to src/commands/default/muxcommand.py index 6209fec197..fc02956a8b 100644 --- a/game/gamesrc/commands/default/muxcommand.py +++ b/src/commands/default/muxcommand.py @@ -3,7 +3,7 @@ The command template for the default MUX-style command set """ from src.utils import utils -from game.gamesrc.commands.basecommand import Command +from src.commands.command import Command class MuxCommand(Command): """ diff --git a/game/gamesrc/commands/default/syscommands.py b/src/commands/default/syscommands.py similarity index 99% rename from game/gamesrc/commands/default/syscommands.py rename to src/commands/default/syscommands.py index d9f9af76d9..b472c91092 100644 --- a/game/gamesrc/commands/default/syscommands.py +++ b/src/commands/default/syscommands.py @@ -17,7 +17,7 @@ to implement a line-editor where you don't have to start each line with a command (if there is no match to a known command, the line is just added to the editor buffer). """ -from game.gamesrc.commands.default.muxcommand import MuxCommand + from src.comms.models import Channel from src.utils import create from src.permissions.permissions import has_perm @@ -31,6 +31,7 @@ from src.commands.cmdhandler import CMD_NOPERM from src.commands.cmdhandler import CMD_CHANNEL from src.commands.cmdhandler import CMD_EXIT +from src.commands.default.muxcommand import MuxCommand # Command called when there is no input at line # (i.e. an lone return key) diff --git a/game/gamesrc/commands/default/system.py b/src/commands/default/system.py similarity index 99% rename from game/gamesrc/commands/default/system.py rename to src/commands/default/system.py index c8dd9e0193..e83fc17ebd 100644 --- a/game/gamesrc/commands/default/system.py +++ b/src/commands/default/system.py @@ -14,7 +14,7 @@ from src.scripts.models import ScriptDB from src.objects.models import ObjectDB from src.config.models import ConfigValue from src.utils import reloads, create, logger, utils, gametime -from game.gamesrc.commands.default.muxcommand import MuxCommand +from src.commands.default.muxcommand import MuxCommand class CmdReload(MuxCommand): """ diff --git a/src/commands/default/tests.py b/src/commands/default/tests.py new file mode 100644 index 0000000000..ac48067876 --- /dev/null +++ b/src/commands/default/tests.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- +""" + ** OBS - this is not a normal command module! ** + ** You cannot import anythin in this module as a command! ** + +This is part of the Evennia unittest framework, for testing the +stability and integrity of the codebase during updates. This module +test the default command set. It is instantiated by the +src/objects/tests.py module, which in turn is run by as part of the +main test suite started with + > python game/manage.py test. + +""" + +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: + from django.test import TestCase +from django.conf import settings +from src.utils import create +from src.server import session, sessionhandler + +#------------------------------------------------------------ +# 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 CommandTest(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(CommandTest): + def test_call(self): + self.char1.home = self.room2 + self.execute_cmd("home") + self.assertEqual(self.char1.location, self.room2) +class TestLook(CommandTest): + def test_call(self): + self.execute_cmd("look here") +class TestPassword(CommandTest): + def test_call(self): + self.execute_cmd("@password testpassword = newpassword") +class TestNick(CommandTest): + def test_call(self): + self.execute_cmd("nickname testalias = testaliasedstring") + self.assertEquals("testaliasedstring", self.char1.nicks.get("testalias", None)) diff --git a/game/gamesrc/commands/default/unimplemented/__init__.py b/src/commands/default/unimplemented/__init__.py similarity index 100% rename from game/gamesrc/commands/default/unimplemented/__init__.py rename to src/commands/default/unimplemented/__init__.py diff --git a/game/gamesrc/commands/default/unimplemented/imc2.py b/src/commands/default/unimplemented/imc2.py similarity index 100% rename from game/gamesrc/commands/default/unimplemented/imc2.py rename to src/commands/default/unimplemented/imc2.py diff --git a/game/gamesrc/commands/default/unimplemented/irc.py b/src/commands/default/unimplemented/irc.py similarity index 100% rename from game/gamesrc/commands/default/unimplemented/irc.py rename to src/commands/default/unimplemented/irc.py diff --git a/game/gamesrc/commands/default/unimplemented/objmanip.py b/src/commands/default/unimplemented/objmanip.py similarity index 100% rename from game/gamesrc/commands/default/unimplemented/objmanip.py rename to src/commands/default/unimplemented/objmanip.py diff --git a/game/gamesrc/commands/default/unimplemented/search.py b/src/commands/default/unimplemented/search.py similarity index 100% rename from game/gamesrc/commands/default/unimplemented/search.py rename to src/commands/default/unimplemented/search.py diff --git a/game/gamesrc/commands/default/unloggedin.py b/src/commands/default/unloggedin.py similarity index 99% rename from game/gamesrc/commands/default/unloggedin.py rename to src/commands/default/unloggedin.py index f760b66a5a..17adb9efb2 100644 --- a/game/gamesrc/commands/default/unloggedin.py +++ b/src/commands/default/unloggedin.py @@ -10,7 +10,7 @@ from src.objects.models import ObjectDB from src.config.models import ConfigValue from src.comms.models import Channel from src.utils import create, logger, utils -from game.gamesrc.commands.default.muxcommand import MuxCommand +from src.commands.default.muxcommand import MuxCommand class CmdConnect(MuxCommand): """ diff --git a/game/gamesrc/commands/default/tests.py b/src/commands/default/utils.py similarity index 99% rename from game/gamesrc/commands/default/tests.py rename to src/commands/default/utils.py index 3c867ccc5c..66dfe7212c 100644 --- a/game/gamesrc/commands/default/tests.py +++ b/src/commands/default/utils.py @@ -8,7 +8,7 @@ from django.db import IntegrityError from src.comms.models import Msg from src.permissions import permissions from src.utils import create, debug, utils -from game.gamesrc.commands.default.muxcommand import MuxCommand +from src.commands.default.muxcommand import MuxCommand from src.commands import cmdsethandler @@ -225,5 +225,3 @@ class TestCom(MuxCommand): caller.msg(string) return caller.msg("Usage: @testcom/create channel") - - diff --git a/src/objects/tests.py b/src/objects/tests.py index 557c0ad8de..e14d1d6f24 100644 --- a/src/objects/tests.py +++ b/src/objects/tests.py @@ -3,7 +3,7 @@ """ Unit testing of the 'objects' Evennia component. -Runs as part of the Evennia's test suite with 'manage.py test-evennia'. +Runs as part of the Evennia's test suite with 'manage.py test" Please add new tests to this module as needed. @@ -14,20 +14,20 @@ Guidelines: Inside the test methods, special member methods assert*() are used to test the behaviour. """ -import re, time +import sys 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 +try: + from django.utils import unittest +except ImportError: + import unittest + from django.conf import settings from src.objects import models, objects from src.utils import create -from src.server import session, sessionhandler +from src.commands.default import tests as commandtests class TestObjAttrs(TestCase): """ @@ -38,8 +38,6 @@ class TestObjAttrs(TestCase): 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 @@ -48,106 +46,13 @@ class TestObjAttrs(TestCase): 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)) +def suite(): + """ + This function is called automatically by the django test runner. + This also runs the command tests defined in src/commands/default/tests.py. + """ + tsuite = unittest.TestSuite() + tsuite.addTest(unittest.defaultTestLoader.loadTestsFromModule(sys.modules[__name__])) + tsuite.addTest(unittest.defaultTestLoader.loadTestsFromModule(commandtests)) + return tsuite diff --git a/src/settings_default.py b/src/settings_default.py index d706a43598..53d8d9493c 100644 --- a/src/settings_default.py +++ b/src/settings_default.py @@ -47,10 +47,10 @@ ALLOW_MULTISESSION = True SECRET_KEY = 'changeme!(*#&*($&*(#*(&SDFKJJKLS*(@#KJAS' # The path that contains this settings.py file (no trailing slash). BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -# Path to the game directory (containing the database file if using sqlite). -GAME_DIR = os.path.join(BASE_PATH, 'game') # Path to the src directory containing the bulk of the codebase's code. SRC_DIR = os.path.join(BASE_PATH, 'src') +# Path to the game directory (containing the database file if using sqlite). +GAME_DIR = os.path.join(BASE_PATH, 'game') # Place to put log files LOG_DIR = os.path.join(GAME_DIR, 'logs') DEFAULT_LOG_FILE = os.path.join(LOG_DIR, 'evennia.log') @@ -136,9 +136,9 @@ ALTERNATE_OBJECT_SEARCH_MULTIMATCH_PARSER = "" ################################################### # Command set used before player has logged in -CMDSET_UNLOGGEDIN = "game.gamesrc.commands.default.cmdset_unloggedin.UnloggedinCmdSet" +CMDSET_UNLOGGEDIN = "src.commands.default.cmdset_unloggedin.UnloggedinCmdSet" # Default set for logged in players (fallback) -CMDSET_DEFAULT = "game.gamesrc.commands.default.cmdset_default.DefaultCmdSet" +CMDSET_DEFAULT = "src.commands.default.cmdset_default.DefaultCmdSet" ################################################### @@ -326,7 +326,7 @@ ADMINS = () #'Your Name', 'your_email@domain.com'),) MANAGERS = ADMINS # Absolute path to the directory that holds media (no trailing slash). # Example: "/home/media/media.lawrence.com" -MEDIA_ROOT = os.path.join(GAME_DIR, 'web', 'media') +MEDIA_ROOT = os.path.join(SRC_DIR, 'web', 'media') # It's safe to dis-regard this, as it's a Django feature we only half use as a # dependency, not actually what it's primarily meant for. SITE_ID = 1 @@ -354,7 +354,7 @@ SERVE_MEDIA = True # The master urlconf file that contains all of the sub-branches to the # applications. -ROOT_URLCONF = 'game.web.urls' +ROOT_URLCONF = 'src.web.urls' # Where users are redirected after logging in via contrib.auth.login. LOGIN_REDIRECT_URL = '/' # Where to redirect users when using the @login_required decorator. @@ -373,8 +373,8 @@ ADMIN_MEDIA_PREFIX = '/media/amedia/' ACTIVE_TEMPLATE = 'prosimii' # We setup the location of the website template as well as the admin site. TEMPLATE_DIRS = ( - os.path.join(GAME_DIR, "web", "templates", ACTIVE_TEMPLATE), - os.path.join(GAME_DIR, "web", "templates"),) + os.path.join(SRC_DIR, "web", "templates", ACTIVE_TEMPLATE), + os.path.join(SRC_DIR, "web", "templates"),) # List of callables that know how to import templates from various sources. TEMPLATE_LOADERS = ( 'django.template.loaders.filesystem.load_template_source', @@ -397,12 +397,10 @@ TEMPLATE_CONTEXT_PROCESSORS = ( 'django.core.context_processors.auth', 'django.core.context_processors.media', 'django.core.context_processors.debug', - 'game.web.utils.general_context.general_context',) -# Use a custom test runner that just tests Evennia-specific apps. -TEST_RUNNER = 'src.utils.test_utils.EvenniaTestSuiteRunner' + 'src.web.utils.general_context.general_context',) ################################################### -# Evennia components (django apps) +# Evennia components ################################################### # Global and Evennia-specific apps. This ties everything together so we can @@ -424,12 +422,13 @@ INSTALLED_APPS = ( 'src.help', 'src.scripts', 'src.permissions', - 'game.web.news', - 'game.web.website',) - + 'src.web.news', + 'src.web.website',) # The user profile extends the User object with more functionality; # This should usually not be changed. AUTH_PROFILE_MODULE = "players.PlayerDB" +# Use a custom test runner that just tests Evennia-specific apps. +TEST_RUNNER = 'src.utils.test_utils.EvenniaTestSuiteRunner' ################################################### # Django extensions @@ -443,7 +442,7 @@ try: except ImportError: pass # South handles automatic database scheme migrations when evennia updates -try: +try: import south INSTALLED_APPS = INSTALLED_APPS + ('south',) except ImportError: diff --git a/src/utils/test_utils.py b/src/utils/test_utils.py index 804059644c..951da588a4 100644 --- a/src/utils/test_utils.py +++ b/src/utils/test_utils.py @@ -1,3 +1,8 @@ +""" +Test runner for Evennia test suite. Run with "game/manage.py test". + +""" + from django.conf import settings from django.test.simple import DjangoTestSuiteRunner @@ -14,4 +19,4 @@ class EvenniaTestSuiteRunner(DjangoTestSuiteRunner): 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) \ No newline at end of file + return super(EvenniaTestSuiteRunner, self).build_suite(test_labels, extra_tests=extra_tests, **kwargs) diff --git a/game/web/__init__.py b/src/web/__init__.py similarity index 100% rename from game/web/__init__.py rename to src/web/__init__.py diff --git a/game/web/media/css/prosimii-print.css b/src/web/media/css/prosimii-print.css similarity index 100% rename from game/web/media/css/prosimii-print.css rename to src/web/media/css/prosimii-print.css diff --git a/game/web/media/css/prosimii-screen-alt.css b/src/web/media/css/prosimii-screen-alt.css similarity index 100% rename from game/web/media/css/prosimii-screen-alt.css rename to src/web/media/css/prosimii-screen-alt.css diff --git a/game/web/media/css/prosimii-screen.css b/src/web/media/css/prosimii-screen.css similarity index 100% rename from game/web/media/css/prosimii-screen.css rename to src/web/media/css/prosimii-screen.css diff --git a/game/web/media/images/LICENCE b/src/web/media/images/LICENCE similarity index 100% rename from game/web/media/images/LICENCE rename to src/web/media/images/LICENCE diff --git a/game/web/media/images/evennia_logo.png b/src/web/media/images/evennia_logo.png similarity index 100% rename from game/web/media/images/evennia_logo.png rename to src/web/media/images/evennia_logo.png diff --git a/game/web/media/images/evennia_logo_small.png b/src/web/media/images/evennia_logo_small.png similarity index 100% rename from game/web/media/images/evennia_logo_small.png rename to src/web/media/images/evennia_logo_small.png diff --git a/game/web/media/images/favicon.ico b/src/web/media/images/favicon.ico similarity index 100% rename from game/web/media/images/favicon.ico rename to src/web/media/images/favicon.ico diff --git a/game/web/news/__init__.py b/src/web/news/__init__.py similarity index 100% rename from game/web/news/__init__.py rename to src/web/news/__init__.py diff --git a/game/web/news/admin.py b/src/web/news/admin.py similarity index 89% rename from game/web/news/admin.py rename to src/web/news/admin.py index 392a96b83a..9e48748f01 100644 --- a/game/web/news/admin.py +++ b/src/web/news/admin.py @@ -4,7 +4,7 @@ # from django.contrib import admin -from game.web.news.models import NewsTopic, NewsEntry +from src.web.news.models import NewsTopic, NewsEntry class NewsTopicAdmin(admin.ModelAdmin): list_display = ('name', 'icon') diff --git a/game/web/news/models.py b/src/web/news/models.py similarity index 100% rename from game/web/news/models.py rename to src/web/news/models.py diff --git a/game/web/news/urls.py b/src/web/news/urls.py similarity index 87% rename from game/web/news/urls.py rename to src/web/news/urls.py index 0ca0259a73..10c891d42c 100755 --- a/game/web/news/urls.py +++ b/src/web/news/urls.py @@ -5,7 +5,7 @@ It is imported from the root handler, game.web.urls.py. from django.conf.urls.defaults import * -urlpatterns = patterns('game.web.news.views', +urlpatterns = patterns('src.web.news.views', (r'^show/(?P\d+)/$', 'show_news'), (r'^archive/$', 'news_archive'), (r'^search/$', 'search_form'), diff --git a/game/web/news/views.py b/src/web/news/views.py similarity index 98% rename from game/web/news/views.py rename to src/web/news/views.py index 10db91714c..a5034b9338 100755 --- a/game/web/news/views.py +++ b/src/web/news/views.py @@ -14,7 +14,7 @@ from django.contrib.auth.models import User from django import forms from django.db.models import Q -from game.web.news.models import NewsTopic, NewsEntry +from src.web.news.models import NewsTopic, NewsEntry # The sidebar text to be included as a variable on each page. There's got to # be a better, cleaner way to include this on every page. diff --git a/game/web/templates/admin/base_site.html b/src/web/templates/admin/base_site.html similarity index 100% rename from game/web/templates/admin/base_site.html rename to src/web/templates/admin/base_site.html diff --git a/game/web/templates/admin/index.html b/src/web/templates/admin/index.html similarity index 100% rename from game/web/templates/admin/index.html rename to src/web/templates/admin/index.html diff --git a/game/web/templates/prosimii/base.html b/src/web/templates/prosimii/base.html similarity index 100% rename from game/web/templates/prosimii/base.html rename to src/web/templates/prosimii/base.html diff --git a/game/web/templates/prosimii/flatpages/default.html b/src/web/templates/prosimii/flatpages/default.html similarity index 100% rename from game/web/templates/prosimii/flatpages/default.html rename to src/web/templates/prosimii/flatpages/default.html diff --git a/game/web/templates/prosimii/index.html b/src/web/templates/prosimii/index.html similarity index 100% rename from game/web/templates/prosimii/index.html rename to src/web/templates/prosimii/index.html diff --git a/game/web/templates/prosimii/news/archive.html b/src/web/templates/prosimii/news/archive.html similarity index 100% rename from game/web/templates/prosimii/news/archive.html rename to src/web/templates/prosimii/news/archive.html diff --git a/game/web/templates/prosimii/news/search_form.html b/src/web/templates/prosimii/news/search_form.html similarity index 100% rename from game/web/templates/prosimii/news/search_form.html rename to src/web/templates/prosimii/news/search_form.html diff --git a/game/web/templates/prosimii/news/show_entry.html b/src/web/templates/prosimii/news/show_entry.html similarity index 100% rename from game/web/templates/prosimii/news/show_entry.html rename to src/web/templates/prosimii/news/show_entry.html diff --git a/game/web/templates/prosimii/registration/logged_out.html b/src/web/templates/prosimii/registration/logged_out.html similarity index 100% rename from game/web/templates/prosimii/registration/logged_out.html rename to src/web/templates/prosimii/registration/logged_out.html diff --git a/game/web/templates/prosimii/registration/login.html b/src/web/templates/prosimii/registration/login.html similarity index 100% rename from game/web/templates/prosimii/registration/login.html rename to src/web/templates/prosimii/registration/login.html diff --git a/game/web/templates/prosimii/tbi.html b/src/web/templates/prosimii/tbi.html similarity index 100% rename from game/web/templates/prosimii/tbi.html rename to src/web/templates/prosimii/tbi.html diff --git a/game/web/urls.py b/src/web/urls.py similarity index 90% rename from game/web/urls.py rename to src/web/urls.py index 221d4e2c0d..c748108353 100755 --- a/game/web/urls.py +++ b/src/web/urls.py @@ -23,12 +23,12 @@ urlpatterns = patterns('', url(r'^accounts/logout', 'django.contrib.auth.views.logout'), # Front page - url(r'^', include('game.web.website.urls')), + url(r'^', include('src.web.website.urls')), # News stuff - url(r'^news/', include('game.web.news.urls')), + url(r'^news/', include('src.web.news.urls')), # Page place-holder for things that aren't implemented yet. - url(r'^tbi/', 'game.web.website.views.to_be_implemented'), + url(r'^tbi/', 'src.web.website.views.to_be_implemented'), # Admin interface url(r'^admin/doc/', include('django.contrib.admindocs.urls')), diff --git a/game/web/utils/__init__.py b/src/web/utils/__init__.py similarity index 100% rename from game/web/utils/__init__.py rename to src/web/utils/__init__.py diff --git a/game/web/utils/apache_wsgi.conf b/src/web/utils/apache_wsgi.conf similarity index 100% rename from game/web/utils/apache_wsgi.conf rename to src/web/utils/apache_wsgi.conf diff --git a/game/web/utils/evennia_modpy_apache.conf b/src/web/utils/evennia_modpy_apache.conf similarity index 100% rename from game/web/utils/evennia_modpy_apache.conf rename to src/web/utils/evennia_modpy_apache.conf diff --git a/game/web/utils/evennia_wsgi_apache.conf b/src/web/utils/evennia_wsgi_apache.conf similarity index 100% rename from game/web/utils/evennia_wsgi_apache.conf rename to src/web/utils/evennia_wsgi_apache.conf diff --git a/game/web/utils/general_context.py b/src/web/utils/general_context.py similarity index 100% rename from game/web/utils/general_context.py rename to src/web/utils/general_context.py diff --git a/game/web/website/__init__.py b/src/web/website/__init__.py similarity index 100% rename from game/web/website/__init__.py rename to src/web/website/__init__.py diff --git a/game/web/website/models.py b/src/web/website/models.py similarity index 100% rename from game/web/website/models.py rename to src/web/website/models.py diff --git a/game/web/website/urls.py b/src/web/website/urls.py similarity index 75% rename from game/web/website/urls.py rename to src/web/website/urls.py index 1f010d471a..b12603afe1 100644 --- a/game/web/website/urls.py +++ b/src/web/website/urls.py @@ -5,6 +5,6 @@ webpage 'application'. from django.conf.urls.defaults import * -urlpatterns = patterns('game.web.website.views', +urlpatterns = patterns('src.web.website.views', (r'^$', 'page_index'), ) diff --git a/game/web/website/views.py b/src/web/website/views.py similarity index 98% rename from game/web/website/views.py rename to src/web/website/views.py index 4315631332..6e23161719 100644 --- a/game/web/website/views.py +++ b/src/web/website/views.py @@ -7,7 +7,7 @@ from src.config.models import ConfigValue from src.objects.models import ObjectDB from src.typeclasses.models import TypedObject from src.players.models import PlayerDB -from game.web.news.models import NewsEntry +from src.web.news.models import NewsEntry """ This file contains the generic, assorted views that don't fall under one of