Fix merge conflicts

This commit is contained in:
Griatch 2019-03-04 22:32:51 +01:00
commit 90b42ca6fb
47 changed files with 1682 additions and 201 deletions

View file

@ -1,4 +1,5 @@
language: python
cache: pip
python:
- "3.6"
sudo: false

View file

@ -58,6 +58,15 @@ Web/Django standard initiative (@strikaco)
- Bugfixes
- Fixes bug on login page where error messages were not being displayed
### Prototypes
- `evennia.prototypes.save_prototype` now takes the prototype as a normal
argument (`prototype`) instead of having to give it as `**prototype`.
- `evennia.prototypes.search_prototype` has a new kwarg `require_single=False` that
raises a KeyError exception if query gave 0 or >1 results.
- `evennia.prototypes.spawner` can now spawn by passing a `prototype_key`
### Typeclasses
- Add new methods on all typeclasses, useful specifically for object handling from the website/admin:
@ -81,7 +90,9 @@ Web/Django standard initiative (@strikaco)
### Utils
- Added more unit tests.
- `evennia` launcher now fully handles all django-admin commands, like running tests in parallel.
- `evennia.utils.create.account` now also takes `tags` and `attrs` keywords.
- Added many more unit tests.
### Server
@ -213,6 +224,11 @@ Web/Django standard initiative (@strikaco)
- `tb_range` - Adds system for abstract positioning and movement.
- Updates and some cleanup of existing contribs.
### Internationalization
- Polish translation by user ogotai
# Overviews
## Sept 2017:

View file

@ -33,7 +33,7 @@ LABEL maintainer="www.evennia.com"
# install compilation environment
RUN apk update && apk add bash gcc jpeg-dev musl-dev procps py-pip \
py-setuptools py2-openssl python python-dev zlib-dev
py-setuptools py2-openssl python python-dev zlib-dev gettext
# add the files required for pip installation
COPY ./setup.py /usr/src/evennia/

View file

@ -1,10 +1,9 @@
"""
Evennia MUD/MUX/MU* creation system
This is the main top-level API for Evennia. You can also explore the
evennia library by accessing evennia.<subpackage> directly. From
inside the game you can read docs of all object by viewing its
`__doc__` string, such as through
This is the main top-level API for Evennia. You can explore the evennia library
by accessing evennia.<subpackage> directly. From inside the game you can read
docs of all object by viewing its `__doc__` string, such as through
@py evennia.ObjectDB.__doc__
@ -18,6 +17,31 @@ See www.evennia.com for full documentation.
"""
# docstring header
DOCSTRING = """
|cEvennia|n 'flat' API (use |wevennia.<component>.__doc__|n to read doc-strings
and |wdict(evennia.component)|n or
|wevennia.component.__dict__ to see contents)
|cTypeclass-bases:|n |cDatabase models|n:
DefaultAccount DefaultObject AccountDB ObjectDB
DefaultGuest DefaultCharacter ChannelDB
DefaultRoom ScriptDB
DefaultChannel DefaultExit Msg
DefaultScript
|cSearch functions:|n |cCommand parents and helpers:|n
search_account search_object default_cmds
search_script search_channel Command InterruptCommand
search_help search_message CmdSet
search_tag managers |cUtilities:|n
|cCreate functions:|n settings lockfuncs
create_account create_object logger gametime
create_script create_channel ansi spawn
create_help_entry create_message contrib managers
|cGlobal handlers:|n set_trace
TICKER_HANDLER TASK_HANDLER EvMenu EvTable
SESSION_HANDLER CHANNEL_HANDLER EvForm EvEditor """
# Delayed loading of properties
# Typeclasses
@ -114,7 +138,6 @@ def _create_version():
__version__ = _create_version()
del _create_version
def _init():
"""
This function is called automatically by the launcher only after
@ -188,6 +211,10 @@ def _init():
from .comms.channelhandler import CHANNEL_HANDLER
from .scripts.monitorhandler import MONITOR_HANDLER
# initialize the doc string
global __doc__
__doc__ = ansi.parse_ansi(DOCSTRING)
# API containers
class _EvContainer(object):
@ -205,15 +232,17 @@ def _init():
class DBmanagers(_EvContainer):
"""
Links to instantiated database managers.
Links to instantiated Django database managers. These are used
to perform more advanced custom database queries than the standard
search functions allow.
helpentry - HelpEntry.objects
helpentries - HelpEntry.objects
accounts - AccountDB.objects
scripts - ScriptDB.objects
msgs - Msg.objects
channels - Channel.objects
objects - ObjectDB.objects
serverconfigs = ServerConfig.objects
serverconfigs - ServerConfig.objects
tags - Tags.objects
attributes - Attributes.objects

View file

@ -1333,19 +1333,28 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)):
# list of targets - make list to disconnect from db
characters = list(tar for tar in target if tar) if target else []
sessions = self.sessions.all()
if not sessions:
# no sessions, nothing to report
return ""
is_su = self.is_superuser
# text shown when looking in the ooc area
result = ["Account |g%s|n (you are Out-of-Character)" % self.key]
nsess = len(sessions)
result.append(nsess == 1 and "\n\n|wConnected session:|n" or "\n\n|wConnected sessions (%i):|n" % nsess)
result.append(nsess == 1 and
"\n\n|wConnected session:|n" or
"\n\n|wConnected sessions (%i):|n" % nsess)
for isess, sess in enumerate(sessions):
csessid = sess.sessid
addr = "%s (%s)" % (sess.protocol_key, isinstance(sess.address, tuple) and
str(sess.address[0]) or str(sess.address))
result.append("\n %s %s" % (session.sessid == csessid and "|w* %s|n" % (isess + 1) or
" %s" % (isess + 1), addr))
str(sess.address[0]) or
str(sess.address))
result.append("\n %s %s" % (
session and
session.sessid == csessid and
"|w* %s|n" % (isess + 1) or
" %s" % (isess + 1), addr))
result.append("\n\n |whelp|n - more commands")
result.append("\n |wooc <Text>|n - talk on public channel")
@ -1487,7 +1496,6 @@ class DefaultGuest(DefaultAccount):
characters = self.db._playable_characters
for character in characters:
if character:
print("deleting Character:", character)
character.delete()
def at_post_disconnect(self, **kwargs):

View file

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
from mock import Mock, MagicMock
import sys
from mock import Mock, MagicMock, patch
from random import randint
from unittest import TestCase
@ -60,19 +61,20 @@ class TestAccountSessionHandler(TestCase):
"Check count method"
self.assertEqual(self.handler.count(), len(self.handler.get()))
@override_settings(GUEST_ENABLED=True, GUEST_LIST=["bruce_wayne"])
class TestDefaultGuest(EvenniaTest):
"Check DefaultGuest class"
ip = '212.216.134.22'
def test_authenticate(self):
@override_settings(GUEST_ENABLED=False)
def test_create_not_enabled(self):
# Guest account should not be permitted
account, errors = DefaultGuest.authenticate(ip=self.ip)
self.assertFalse(account, 'Guest account was created despite being disabled.')
settings.GUEST_ENABLED = True
settings.GUEST_LIST = ['bruce_wayne']
def test_authenticate(self):
# Create a guest account
account, errors = DefaultGuest.authenticate(ip=self.ip)
self.assertTrue(account, 'Guest account should have been created.')
@ -81,7 +83,32 @@ class TestDefaultGuest(EvenniaTest):
account, errors = DefaultGuest.authenticate(ip=self.ip)
self.assertFalse(account, 'Two guest accounts were created with a single entry on the guest list!')
settings.GUEST_ENABLED = False
@patch("evennia.accounts.accounts.ChannelDB.objects.get_channel")
def test_create(self, get_channel):
get_channel.connect = MagicMock(return_value=True)
account, errors = DefaultGuest.create()
self.assertTrue(account, "Guest account should have been created.")
self.assertFalse(errors)
def test_at_post_login(self):
self.account.db._last_puppet = self.char1
self.account.at_post_login(self.session)
self.account.at_post_login()
def test_at_server_shutdown(self):
account, errors = DefaultGuest.create(ip=self.ip)
self.char1.delete = MagicMock()
account.db._playable_characters = [self.char1]
account.at_server_shutdown()
self.char1.delete.assert_called()
def test_at_post_disconnect(self):
account, errors = DefaultGuest.create(ip=self.ip)
self.char1.delete = MagicMock()
account.db._playable_characters = [self.char1]
account.at_post_disconnect()
self.char1.delete.assert_called()
class TestDefaultAccountAuth(EvenniaTest):
@ -162,6 +189,7 @@ class TestDefaultAccountAuth(EvenniaTest):
self.assertFalse(account.set_password('Mxyzptlk'))
account.delete()
class TestDefaultAccount(TestCase):
"Check DefaultAccount class"
@ -279,13 +307,83 @@ class TestAccountPuppetDeletion(EvenniaTest):
@override_settings(MULTISESSION_MODE=2)
def test_puppet_deletion(self):
# Check for existing chars
self.assertFalse(self.account.db._playable_characters, 'Account should not have any chars by default.')
self.assertFalse(self.account.db._playable_characters,
'Account should not have any chars by default.')
# Add char1 to account's playable characters
self.account.db._playable_characters.append(self.char1)
self.assertTrue(self.account.db._playable_characters, 'Char was not added to account.')
self.assertTrue(self.account.db._playable_characters,
'Char was not added to account.')
# See what happens when we delete char1.
self.char1.delete()
# Playable char list should be empty.
self.assertFalse(self.account.db._playable_characters, 'Playable character list is not empty! %s' % self.account.db._playable_characters)
self.assertFalse(self.account.db._playable_characters,
'Playable character list is not empty! %s' % self.account.db._playable_characters)
class TestDefaultAccountEv(EvenniaTest):
"""
Testing using the EvenniaTest parent
"""
def test_characters_property(self):
"test existence of None in _playable_characters Attr"
self.account.db._playable_characters = [self.char1, None]
chars = self.account.characters
self.assertEqual(chars, [self.char1])
self.assertEqual(self.account.db._playable_characters, [self.char1])
def test_puppet_success(self):
self.account.msg = MagicMock()
with patch("evennia.accounts.accounts._MULTISESSION_MODE", 2):
self.account.puppet_object(self.session, self.char1)
self.account.msg.assert_called_with("You are already puppeting this object.")
@patch("evennia.accounts.accounts.time.time", return_value=10000)
def test_idle_time(self, mock_time):
self.session.cmd_last_visible = 10000 - 10
idle = self.account.idle_time
self.assertEqual(idle, 10)
# test no sessions
with patch("evennia.accounts.accounts._SESSIONS.sessions_from_account", return_value=[]) as mock_sessh:
idle = self.account.idle_time
self.assertEqual(idle, None)
@patch("evennia.accounts.accounts.time.time", return_value=10000)
def test_connection_time(self, mock_time):
self.session.conn_time = 10000 - 10
conn = self.account.connection_time
self.assertEqual(conn, 10)
# test no sessions
with patch("evennia.accounts.accounts._SESSIONS.sessions_from_account", return_value=[]) as mock_sessh:
idle = self.account.connection_time
self.assertEqual(idle, None)
def test_create_account(self):
acct = create.account(
"TestAccount3", "test@test.com", "testpassword123",
locks="test:all()",
tags=[("tag1", "category1"), ("tag2", "category2", "data1"), ("tag3", None)],
attributes=[("key1", "value1", "category1",
"edit:false()", True),
("key2", "value2")])
acct.save()
self.assertTrue(acct.pk)
def test_at_look(self):
ret = self.account.at_look()
self.assertTrue("Out-of-Character" in ret)
ret = self.account.at_look(target=self.obj1)
self.assertTrue("Obj" in ret)
ret = self.account.at_look(session=self.session)
self.assertTrue("*" in ret) # * marks session is active in list
ret = self.account.at_look(target=self.obj1, session=self.session)
self.assertTrue("Obj" in ret)
ret = self.account.at_look(target="Invalid", session=self.session)
self.assertEqual(ret, 'Invalid has no in-game appearance.')
def test_msg(self):
self.account.msg

View file

@ -1040,6 +1040,11 @@ class CmdLink(COMMAND_DEFAULT_CLASS):
if not target:
return
if target == obj:
self.caller.msg("Cannot link an object to itself.")
return
string = ""
note = "Note: %s(%s) did not have a destination set before. Make sure you linked the right thing."
if not obj.destination:
@ -1123,6 +1128,7 @@ class CmdSetHome(CmdLink):
Usage:
@sethome <obj> [= <home_location>]
@sethom <obj>
The "home" location is a "safety" location for objects; they
will be moved there if their current location ceases to exist. All
@ -1162,10 +1168,10 @@ class CmdSetHome(CmdLink):
old_home = obj.home
obj.home = new_home
if old_home:
string = "%s's home location was changed from %s(%s) to %s(%s)." % (
string = "Home location of %s was changed from %s(%s) to %s(%s)." % (
obj, old_home, old_home.dbref, new_home, new_home.dbref)
else:
string = "%s' home location was set to %s(%s)." % (obj, new_home, new_home.dbref)
string = "Home location of %s was set to %s(%s)." % (obj, new_home, new_home.dbref)
self.caller.msg(string)
@ -1430,37 +1436,6 @@ def _convert_from_string(cmd, strobj):
string this will always fail).
"""
def rec_convert(obj):
"""
Helper function of recursive conversion calls. This is only
used for Python <=2.5. After that literal_eval is available.
"""
# simple types
try:
return int(obj)
except ValueError:
# obj cannot be converted to int - that's fine
pass
try:
return float(obj)
except ValueError:
# obj cannot be converted to float - that's fine
pass
# iterables
if obj.startswith('[') and obj.endswith(']'):
"A list. Traverse recursively."
return [rec_convert(val) for val in obj[1:-1].split(',')]
if obj.startswith('(') and obj.endswith(')'):
"A tuple. Traverse recursively."
return tuple([rec_convert(val) for val in obj[1:-1].split(',')])
if obj.startswith('{') and obj.endswith('}') and ':' in obj:
"A dict. Traverse recursively."
return dict([(rec_convert(pair.split(":", 1)[0]),
rec_convert(pair.split(":", 1)[1]))
for pair in obj[1:-1].split(',') if ":" in pair])
# if nothing matches, return as-is
return obj
# Use literal_eval to parse python structure exactly.
try:
return _LITERAL_EVAL(strobj)
@ -1471,10 +1446,9 @@ def _convert_from_string(cmd, strobj):
"Make sure this is acceptable." % strobj
cmd.caller.msg(string)
return strobj
else:
# fall back to old recursive solution (does not support
# nested lists/dicts)
return rec_convert(strobj.strip())
except Exception as err:
string = "|RUnknown error in evaluating Attribute: {}".format(err)
return string
class CmdSetAttribute(ObjManipCommand):
@ -1957,7 +1931,7 @@ class CmdLock(ObjManipCommand):
caller = self.caller
if not self.args:
string = "@lock <object>[ = <lockstring>] or @lock[/switch] " \
string = "Usage: @lock <object>[ = <lockstring>] or @lock[/switch] " \
"<object>/<access_type>"
caller.msg(string)
return
@ -1978,8 +1952,8 @@ class CmdLock(ObjManipCommand):
caller.msg("You need 'control' access to change this type of lock.")
return
if not has_control_access or obj.access(caller, "edit"):
caller.msg("You are not allowed to do that.")
if not has_control_access or not obj.access(caller, "edit"):
caller.msg("You need 'edit' access to view or delete lock on this object.")
return
lockdef = obj.locks.get(access_type)
@ -2753,11 +2727,11 @@ class CmdTag(COMMAND_DEFAULT_CLASS):
obj)
else:
# no tag specified, clear all tags
old_tags = ["%s%s" % (tag, " (category: %s" % category if category else "")
old_tags = ["%s%s" % (tag, " (category: %s)" % category if category else "")
for tag, category in obj.tags.all(return_key_and_category=True)]
if old_tags:
obj.tags.clear()
string = "Cleared all tags from %s: %s" % (obj, ", ".join(old_tags))
string = "Cleared all tags from %s: %s" % (obj, ", ".join(sorted(old_tags)))
else:
string = "No Tags to clear on %s." % obj
self.caller.msg(string)
@ -2788,8 +2762,9 @@ class CmdTag(COMMAND_DEFAULT_CLASS):
tags = [tup[0] for tup in tagtuples]
categories = [" (category: %s)" % tup[1] if tup[1] else "" for tup in tagtuples]
if ntags:
string = "Tag%s on %s: %s" % ("s" if ntags > 1 else "", obj,
", ".join("'%s'%s" % (tags[i], categories[i]) for i in range(ntags)))
string = "Tag%s on %s: %s" % (
"s" if ntags > 1 else "", obj,
", ".join(sorted("'%s'%s" % (tags[i], categories[i]) for i in range(ntags))))
else:
string = "No tags attached to %s." % obj
self.caller.msg(string)
@ -3002,7 +2977,7 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS):
# all seems ok. Try to save.
try:
prot = protlib.save_prototype(**prototype)
prot = protlib.save_prototype(prototype)
if not prot:
caller.msg("|rError saving:|R {}.|n".format(prototype_key))
return
@ -3091,7 +3066,7 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS):
return
elif nprots > 1:
caller.msg("Found {} prototypes matching '{}':\n {}".format(
nprots, prototype, ", ".join(prot.get('prototype_key', '')
nprots, prototype, ", ".join(proto.get('prototype_key', '')
for proto in prototypes)))
return
# we have a prototype, check access

View file

@ -7,6 +7,7 @@ System commands
import traceback
import os
import io
import datetime
import sys
import django
@ -21,7 +22,7 @@ from evennia.accounts.models import AccountDB
from evennia.utils import logger, utils, gametime, create, search
from evennia.utils.eveditor import EvEditor
from evennia.utils.evtable import EvTable
from evennia.utils.utils import crop, class_from_module
from evennia.utils.utils import crop, class_from_module, to_unicode
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
@ -197,6 +198,7 @@ def _run_code_snippet(caller, pycode, mode="eval", measure_time=False,
duration = " (runtime ~ %.4f ms)" % ((t1 - t0) * 1000)
else:
ret = eval(pycode_compiled, {}, available_vars)
if mode == "eval":
ret = "%s%s" % (str(ret), duration)
else:
@ -238,7 +240,9 @@ class CmdPy(COMMAND_DEFAULT_CLASS):
inherits_from(obj, parent) : check object inheritance
You can explore The evennia API from inside the game by calling
evennia.help(), evennia.managers.help() etc.
the `__doc__` property on entities:
@py evennia.__doc__
@py evennia.managers.__doc__
|rNote: In the wrong hands this command is a severe security risk.
It should only be accessible by trusted server admins/superusers.|n

View file

@ -11,10 +11,10 @@ main test suite started with
> python game/manage.py test.
"""
import re
import types
import datetime
from anything import Anything
from django.conf import settings
from mock import Mock, mock
@ -49,7 +49,7 @@ class CommandTest(EvenniaTest):
Tests a command
"""
def call(self, cmdobj, args, msg=None, cmdset=None, noansi=True, caller=None,
receiver=None, cmdstring=None, obj=None, inputs=None):
receiver=None, cmdstring=None, obj=None, inputs=None, raw_string=None):
"""
Test a command by assigning all the needed
properties to cmdobj and running
@ -74,7 +74,7 @@ class CommandTest(EvenniaTest):
cmdobj.cmdset = cmdset
cmdobj.session = SESSIONS.session_from_sessid(1)
cmdobj.account = self.account
cmdobj.raw_string = cmdobj.key + " " + args
cmdobj.raw_string = raw_string if raw_string is not None else cmdobj.key + " " + args
cmdobj.obj = obj or (caller if caller else self.char1)
# test
old_msg = receiver.msg
@ -322,30 +322,90 @@ class TestBuilding(CommandTest):
name = settings.BASE_OBJECT_TYPECLASS.rsplit('.', 1)[1]
self.call(building.CmdCreate(), "/d TestObj1", # /d switch is abbreviated form of /drop
"You create a new %s: TestObj1." % name)
self.call(building.CmdCreate(), "", "Usage: ")
self.call(building.CmdCreate(), "TestObj1;foo;bar",
"You create a new %s: TestObj1 (aliases: foo, bar)." % name)
def test_examine(self):
self.call(building.CmdExamine(), "", "Name/key: Room")
self.call(building.CmdExamine(), "Obj", "Name/key: Obj")
self.call(building.CmdExamine(), "Obj", "Name/key: Obj")
self.call(building.CmdExamine(), "*TestAccount", "Name/key: TestAccount")
self.char1.db.test = "testval"
self.call(building.CmdExamine(), "self/test", "Persistent attributes:\n test = testval")
self.call(building.CmdExamine(), "NotFound", "Could not find 'NotFound'.")
self.call(building.CmdExamine(), "out", "Name/key: out")
self.room1.scripts.add(self.script.__class__)
self.call(building.CmdExamine(), "")
self.account.scripts.add(self.script.__class__)
self.call(building.CmdExamine(), "*TestAccount")
def test_set_obj_alias(self):
self.call(building.CmdSetObjAlias(), "Obj =", "Cleared aliases from Obj(#4)")
self.call(building.CmdSetObjAlias(), "Obj =", "Cleared aliases from Obj")
self.call(building.CmdSetObjAlias(), "Obj = TestObj1b", "Alias(es) for 'Obj(#4)' set to 'testobj1b'.")
self.call(building.CmdSetObjAlias(), "", "Usage: ")
self.call(building.CmdSetObjAlias(), "NotFound =", "Could not find 'NotFound'.")
self.call(building.CmdSetObjAlias(), "Obj", "Aliases for Obj(#4): 'testobj1b'")
self.call(building.CmdSetObjAlias(), "Obj2 =", "Cleared aliases from Obj2")
self.call(building.CmdSetObjAlias(), "Obj2 =", "No aliases to clear.")
def test_copy(self):
self.call(building.CmdCopy(), "Obj = TestObj2;TestObj2b, TestObj3;TestObj3b",
"Copied Obj to 'TestObj3' (aliases: ['TestObj3b']")
self.call(building.CmdCopy(), "", "Usage: ")
self.call(building.CmdCopy(), "Obj", "Identical copy of Obj, named 'Obj_copy' was created.")
self.call(building.CmdCopy(), "NotFound = Foo", "Could not find 'NotFound'.")
def test_attribute_commands(self):
self.call(building.CmdSetAttribute(), "", "Usage: ")
self.call(building.CmdSetAttribute(), "Obj/test1=\"value1\"", "Created attribute Obj/test1 = 'value1'")
self.call(building.CmdSetAttribute(), "Obj2/test2=\"value2\"", "Created attribute Obj2/test2 = 'value2'")
self.call(building.CmdSetAttribute(), "Obj2/test2", "Attribute Obj2/test2 = value2")
self.call(building.CmdSetAttribute(), "Obj2/NotFound", "Obj2 has no attribute 'notfound'.")
with mock.patch("evennia.commands.default.building.EvEditor") as mock_ed:
self.call(building.CmdSetAttribute(), "/edit Obj2/test3")
mock_ed.assert_called_with(self.char1, Anything, Anything, key='Obj2/test3')
self.call(building.CmdSetAttribute(), "Obj2/test3=\"value3\"", "Created attribute Obj2/test3 = 'value3'")
self.call(building.CmdSetAttribute(), "Obj2/test3 = ", "Deleted attribute 'test3' (= True) from Obj2.")
self.call(building.CmdCpAttr(), "/copy Obj2/test2 = Obj2/test3",
"@cpattr: Extra switch \"/copy\" ignored.|\nCopied Obj2.test2 -> Obj2.test3. "
"(value: 'value2')")
self.call(building.CmdMvAttr(), "", "Usage: ")
self.call(building.CmdMvAttr(), "Obj2/test2 = Obj/test3", "Moved Obj2.test2 -> Obj.test3")
self.call(building.CmdCpAttr(), "", "Usage: ")
self.call(building.CmdCpAttr(), "Obj/test1 = Obj2/test3", "Copied Obj.test1 -> Obj2.test3")
self.call(building.CmdWipe(), "", "Usage: ")
self.call(building.CmdWipe(), "Obj2/test2/test3", "Wiped attributes test2,test3 on Obj2.")
self.call(building.CmdWipe(), "Obj2", "Wiped all attributes on Obj2.")
def test_name(self):
self.call(building.CmdName(), "", "Usage: ")
self.call(building.CmdName(), "Obj2=Obj3", "Object's name changed to 'Obj3'.")
self.call(building.CmdName(), "*TestAccount=TestAccountRenamed",
"Account's name changed to 'TestAccountRenamed'.")
self.call(building.CmdName(), "*NotFound=TestAccountRenamed",
"Could not find '*NotFound'")
self.call(building.CmdName(), "Obj3=Obj4;foo;bar",
"Object's name changed to 'Obj4' (foo, bar).")
self.call(building.CmdName(), "Obj4=", "No names or aliases defined!")
def test_desc(self):
self.call(building.CmdDesc(), "Obj2=TestDesc", "The description was set on Obj2(#5).")
self.call(building.CmdDesc(), "", "Usage: ")
with mock.patch("evennia.commands.default.building.EvEditor") as mock_ed:
self.call(building.CmdDesc(), "/edit")
mock_ed.assert_called_with(self.char1, key='desc',
loadfunc=building._desc_load,
quitfunc=building._desc_quit,
savefunc=building._desc_save,
persistent=True)
def test_empty_desc(self):
"""
@ -365,50 +425,128 @@ class TestBuilding(CommandTest):
assert self.obj2.db.desc == o2d
assert self.room1.db.desc == 'Obj2' and self.room1.db.desc != r1d
def test_wipe(self):
def test_destroy(self):
confirm = building.CmdDestroy.confirm
building.CmdDestroy.confirm = False
self.call(building.CmdDestroy(), "", "Usage: ")
self.call(building.CmdDestroy(), "Obj", "Obj was destroyed.")
self.call(building.CmdDestroy(), "Obj", "Obj2 was destroyed.")
self.call(building.CmdDestroy(), "Obj", "Could not find 'Obj'.| (Objects to destroy "
"must either be local or specified with a unique #dbref.)")
self.call(building.CmdDestroy(), "#1", "You are trying to delete") # DEFAULT_HOME
self.char2.location = self.room2
self.call(building.CmdDestroy(), self.room2.dbref,
"Char2(#7) arrives to Room(#1) from Room2(#2).|Room2 was destroyed.")
building.CmdDestroy.confirm = confirm
def test_destroy_sequence(self):
confirm = building.CmdDestroy.confirm
building.CmdDestroy.confirm = False
self.call(building.CmdDestroy(),
"{}-{}".format(self.obj1.dbref, self.obj2.dbref),
"Obj was destroyed.\nObj2 was destroyed.")
def test_dig(self):
self.call(building.CmdDig(), "TestRoom1=testroom;tr,back;b", "Created room TestRoom1")
self.call(building.CmdDig(), "", "Usage: ")
def test_tunnel(self):
self.call(building.CmdTunnel(), "n = TestRoom2;test2", "Created room TestRoom2")
self.call(building.CmdTunnel(), "", "Usage: ")
self.call(building.CmdTunnel(), "foo = TestRoom2;test2", "@tunnel can only understand the")
self.call(building.CmdTunnel(), "/tel e = TestRoom3;test3",
"Created room TestRoom3(#11) (test3) of type typeclasses.rooms.Room.\n"
"Created Exit from Room to TestRoom3: east(#12) (e).\n"
"Created Exit back from TestRoom3 to Room: west(#13) (w).|TestRoom3(#11)")
def test_tunnel_exit_typeclass(self):
self.call(building.CmdTunnel(), "n:evennia.objects.objects.DefaultExit = TestRoom3", "Created room TestRoom3")
self.call(building.CmdTunnel(), "n:evennia.objects.objects.DefaultExit = TestRoom3",
"Created room TestRoom3")
def test_exit_commands(self):
self.call(building.CmdOpen(), "TestExit1=Room2", "Created new Exit 'TestExit1' from Room to Room2")
self.call(building.CmdLink(), "TestExit1=Room", "Link created TestExit1 -> Room (one way).")
self.call(building.CmdUnLink(), "", "Usage: ")
self.call(building.CmdLink(), "NotFound", "Could not find 'NotFound'.")
self.call(building.CmdLink(), "TestExit", "TestExit1 is an exit to Room.")
self.call(building.CmdLink(), "Obj", "Obj is not an exit. Its home location is Room.")
self.call(building.CmdUnLink(), "TestExit1", "Former exit TestExit1 no longer links anywhere.")
self.char1.location = self.room2
self.call(building.CmdOpen(), "TestExit2=Room", "Created new Exit 'TestExit2' from Room2 to Room.")
self.call(building.CmdOpen(), "TestExit2=Room", "Exit TestExit2 already exists. It already points to the correct place.")
# ensure it matches locally first
self.call(building.CmdLink(), "TestExit=Room2", "Link created TestExit2 -> Room2 (one way).")
self.call(building.CmdLink(), "/twoway TestExit={}".format(self.exit.dbref),
"Link created TestExit2 (in Room2) <-> out (in Room) (two-way).")
self.call(building.CmdLink(), "/twoway TestExit={}".format(self.room1.dbref),
"To create a two-way link, TestExit2 and Room must both have a location ")
self.call(building.CmdLink(), "/twoway {}={}".format(self.exit.dbref, self.exit.dbref),
"Cannot link an object to itself.")
self.call(building.CmdLink(), "", "Usage: ")
# ensure can still match globally when not a local name
self.call(building.CmdLink(), "TestExit1=Room2", "Note: TestExit1(#8) did not have a destination set before. "
"Make sure you linked the right thing.\n"
"Link created TestExit1 -> Room2 (one way).")
self.call(building.CmdLink(), "TestExit1=", "Former exit TestExit1 no longer links anywhere.")
def test_set_home(self):
self.call(building.CmdSetHome(), "Obj = Room2", "Obj's home location was changed from Room")
self.call(building.CmdSetHome(), "Obj = Room2", "Home location of Obj was changed from Room")
self.call(building.CmdSetHome(), "", "Usage: ")
self.call(building.CmdSetHome(), "self", "Char's current home is Room")
self.call(building.CmdSetHome(), "Obj", "Obj's current home is Room2")
self.obj1.home = None
self.call(building.CmdSetHome(), "Obj = Room2", "Home location of Obj was set to Room")
def test_list_cmdsets(self):
self.call(building.CmdListCmdSets(), "", "<DefaultCharacter (Union, prio 0, perm)>:")
self.call(building.CmdListCmdSets(), "NotFound", "Could not find 'NotFound'")
def test_typeclass(self):
self.call(building.CmdTypeclass(), "", "Usage: ")
self.call(building.CmdTypeclass(), "Obj = evennia.objects.objects.DefaultExit",
"Obj changed typeclass from evennia.objects.objects.DefaultObject "
"to evennia.objects.objects.DefaultExit.")
self.call(building.CmdTypeclass(), "Obj2 = evennia.objects.objects.DefaultExit",
"Obj2 changed typeclass from evennia.objects.objects.DefaultObject "
"to evennia.objects.objects.DefaultExit.", cmdstring="@swap")
self.call(building.CmdTypeclass(), "/list Obj", "Core typeclasses")
self.call(building.CmdTypeclass(), "/show Obj", "Obj's current typeclass is 'evennia.objects.objects.DefaultExit'")
self.call(building.CmdTypeclass(), "Obj = evennia.objects.objects.DefaultExit",
"Obj already has the typeclass 'evennia.objects.objects.DefaultExit'. Use /force to override.")
self.call(building.CmdTypeclass(), "/force Obj = evennia.objects.objects.DefaultExit",
"Obj updated its existing typeclass ")
self.call(building.CmdTypeclass(), "Obj = evennia.objects.objects.DefaultObject")
self.call(building.CmdTypeclass(), "/show Obj", "Obj's current typeclass is 'evennia.objects.objects.DefaultObject'")
self.call(building.CmdTypeclass(), "Obj",
"Obj updated its existing typeclass (evennia.objects.objects.DefaultObject).\n"
"Only the at_object_creation hook was run (update mode). Attributes set before swap were not removed.",
cmdstring="@update")
self.call(building.CmdTypeclass(), "/reset/force Obj=evennia.objects.objects.DefaultObject",
"Obj updated its existing typeclass (evennia.objects.objects.DefaultObject).\n"
"All object creation hooks were run. All old attributes where deleted before the swap.")
def test_lock(self):
self.call(building.CmdLock(), "Obj = test:perm(Developer)", "Added lock 'test:perm(Developer)' to Obj.")
self.call(building.CmdLock(), "", "Usage: ")
self.call(building.CmdLock(), "Obj = test:all()", "Added lock 'test:all()' to Obj.")
self.call(building.CmdLock(), "*TestAccount = test:all()", "Added lock 'test:all()' to TestAccount")
self.call(building.CmdLock(), "Obj/notfound", "Obj has no lock of access type 'notfound'.")
self.call(building.CmdLock(), "Obj/test", "test:all()")
self.call(building.CmdLock(), "/view Obj = edit:false()",
"Switch(es) view can not be used with a lock assignment. "
"Use e.g. @lock/del objname/locktype instead.")
self.call(building.CmdLock(), "Obj = edit:false()")
self.call(building.CmdLock(), "Obj/test", "You need 'edit' access to view or delete lock on this object.")
self.call(building.CmdLock(), "Obj", "call:true()") # etc
self.call(building.CmdLock(), "*TestAccount", "boot:perm(Admin)") # etc
def test_find(self):
self.call(building.CmdFind(), "", "Usage: ")
self.call(building.CmdFind(), "oom2", "One Match")
self.call(building.CmdFind(), "oom2 = 1-100", "One Match")
self.call(building.CmdFind(), "oom2 = 1 100", "One Match") # space works too
expect = "One Match(#1-#7, loc):\n " +\
"Char2(#7) - evennia.objects.objects.DefaultCharacter (location: Room(#1))"
self.call(building.CmdFind(), "Char2", expect, cmdstring="locate")
@ -420,11 +558,43 @@ class TestBuilding(CommandTest):
self.call(building.CmdFind(), "Char2", "One Match", cmdstring="@find")
self.call(building.CmdFind(), "/startswith Room2", "One Match")
self.call(building.CmdFind(), self.char1.dbref,
"Exact dbref match(#1-#7):\n "
" Char(#6) - evennia.objects.objects.DefaultCharacter")
self.call(building.CmdFind(), "*TestAccount",
"Match(#1-#7):\n"
" TestAccount - evennia.accounts.accounts.DefaultAccount")
self.call(building.CmdFind(), "/char Obj")
self.call(building.CmdFind(), "/room Obj")
self.call(building.CmdFind(), "/exit Obj")
self.call(building.CmdFind(), "/exact Obj", "One Match")
def test_script(self):
self.call(building.CmdScript(), "Obj = ", "No scripts defined on Obj")
self.call(building.CmdScript(), "Obj = scripts.Script", "Script scripts.Script successfully added")
self.call(building.CmdScript(), "", "Usage: ")
self.call(building.CmdScript(), "= Obj", "To create a global script you need @scripts/add <typeclass>.")
self.call(building.CmdScript(), "Obj = ", "dbref obj")
self.call(building.CmdScript(), "/start Obj", "0 scripts started on Obj") # because it's already started
self.call(building.CmdScript(), "/stop Obj", "Stopping script")
self.call(building.CmdScript(), "Obj = scripts.Script", "Script scripts.Script successfully added")
self.call(building.CmdScript(), "/start Obj = scripts.Script", "Script scripts.Script could not be (re)started.")
self.call(building.CmdScript(), "/stop Obj = scripts.Script", "Script stopped and removed from object.")
def test_teleport(self):
self.call(building.CmdTeleport(), "/quiet Room2", "Room2(#2)\n|Teleported to Room2.")
self.call(building.CmdTeleport(), "", "Usage: ")
self.call(building.CmdTeleport(), "Obj = Room", "Obj is already at Room.")
self.call(building.CmdTeleport(), "Obj = NotFound", "Could not find 'NotFound'.|Destination not found.")
self.call(building.CmdTeleport(),
"Obj = Room2", "Obj(#4) is leaving Room(#1), heading for Room2(#2).|Teleported Obj -> Room2.")
self.call(building.CmdTeleport(), "NotFound = Room", "Could not find 'NotFound'.")
self.call(building.CmdTeleport(), "Obj = Obj", "You can't teleport an object inside of itself!")
self.call(building.CmdTeleport(), "/tonone Obj2", "Teleported Obj2 -> None-location.")
self.call(building.CmdTeleport(), "/quiet Room2", "Room2(#2)")
self.call(building.CmdTeleport(), "/t", # /t switch is abbreviated form of /tonone
"Cannot teleport a puppeted object (Char, puppeted by TestAccount(account 1)) to a None-location.")
self.call(building.CmdTeleport(), "/l Room2", # /l switch is abbreviated form of /loc
@ -432,6 +602,28 @@ class TestBuilding(CommandTest):
self.call(building.CmdTeleport(), "/q me to Room2", # /q switch is abbreviated form of /quiet
"Char is already at Room2.")
def test_tag(self):
self.call(building.CmdTag(), "", "Usage: ")
self.call(building.CmdTag(), "Obj = testtag")
self.call(building.CmdTag(), "Obj = testtag2")
self.call(building.CmdTag(), "Obj = testtag2:category1")
self.call(building.CmdTag(), "Obj = testtag3")
self.call(building.CmdTag(), "Obj", "Tags on Obj: 'testtag', 'testtag2', "
"'testtag2' (category: category1), 'testtag3'")
self.call(building.CmdTag(), "/search NotFound", "No objects found with tag 'NotFound'.")
self.call(building.CmdTag(), "/search testtag", "Found 1 object with tag 'testtag':")
self.call(building.CmdTag(), "/search testtag2", "Found 1 object with tag 'testtag2':")
self.call(building.CmdTag(), "/search testtag2:category1",
"Found 1 object with tag 'testtag2' (category: 'category1'):")
self.call(building.CmdTag(), "/del Obj = testtag3", "Removed tag 'testtag3' from Obj.")
self.call(building.CmdTag(), "/del Obj",
"Cleared all tags from Obj: testtag, testtag2, testtag2 (category: category1)")
def test_spawn(self):
def getObject(commandTest, objKeyStr):
# A helper function to get a spawned object and
@ -453,6 +645,14 @@ class TestBuilding(CommandTest):
"'typeclass':'evennia.objects.objects.DefaultCharacter'}",
"Saved prototype: testprot", inputs=['y'])
self.call(building.CmdSpawn(), "/search ", "Key ")
self.call(building.CmdSpawn(), "/search test;test2", "")
self.call(building.CmdSpawn(),
"/save {'key':'Test Char', "
"'typeclass':'evennia.objects.objects.DefaultCharacter'}",
"To save a prototype it must have the 'prototype_key' set.")
self.call(building.CmdSpawn(), "/list", "Key ")
self.call(building.CmdSpawn(), 'testprot', "Spawned Test Char")
@ -480,9 +680,9 @@ class TestBuilding(CommandTest):
goblin.delete()
# create prototype
protlib.create_prototype(**{'key': 'Ball',
'typeclass': 'evennia.objects.objects.DefaultCharacter',
'prototype_key': 'testball'})
protlib.create_prototype({'key': 'Ball',
'typeclass': 'evennia.objects.objects.DefaultCharacter',
'prototype_key': 'testball'})
# Tests "@spawn <prototype_name>"
self.call(building.CmdSpawn(), "testball", "Spawned Ball")

View file

@ -90,7 +90,7 @@ def display_meter(cur_value, max_value,
# Pick which fill color to use based on how full the bar is
fillcolor_index = (float(len(fill_color)) * percent_full)
fillcolor_index = int(round(fillcolor_index)) - 1
fillcolor_index = max(0, int(round(fillcolor_index)) - 1)
fillcolor_code = "|[" + fill_color[fillcolor_index]
# Make color codes for empty bar portion and text_color

View file

@ -2,17 +2,18 @@
Module containing the test cases for the Audit system.
"""
from django.test import override_settings
from django.conf import settings
from evennia.utils.test_resources import EvenniaTest
import re
# Configure session auditing settings
# Configure session auditing settings - TODO: This is bad practice that leaks over to other tests
settings.AUDIT_CALLBACK = "evennia.security.contrib.auditing.outputs.to_syslog"
settings.AUDIT_IN = True
settings.AUDIT_OUT = True
settings.AUDIT_ALLOW_SPARSE = True
# Configure settings to use custom session
# Configure settings to use custom session - TODO: This is bad practice, changing global settings
settings.SERVER_SESSION_CLASS = "evennia.contrib.security.auditing.server.AuditedServerSession"

View file

@ -691,8 +691,21 @@ from evennia.contrib import health_bar
class TestHealthBar(EvenniaTest):
def test_healthbar(self):
expected_bar_str = "|[G|w |n|[B|w test24 / 200test |n"
self.assertTrue(health_bar.display_meter(24, 200, length=40, pre_text="test", post_text="test", align="center") == expected_bar_str)
expected_bar_str = '|[R|w|n|[B|w test0 / 200test |n'
self.assertEqual(health_bar.display_meter(
0, 200, length=40, pre_text="test", post_text="test", align="center"), expected_bar_str)
expected_bar_str = "|[R|w |n|[B|w test24 / 200test |n"
self.assertEqual(health_bar.display_meter(
24, 200, length=40, pre_text="test", post_text="test", align="center"), expected_bar_str)
expected_bar_str = '|[Y|w test100 /|n|[B|w 200test |n'
self.assertEqual(health_bar.display_meter(
100, 200, length=40, pre_text="test", post_text="test", align="center"), expected_bar_str)
expected_bar_str = '|[G|w test180 / 200test |n|[B|w |n'
self.assertEqual(health_bar.display_meter(
180, 200, length=40, pre_text="test", post_text="test", align="center"), expected_bar_str)
expected_bar_str = '|[G|w test200 / 200test |n|[B|w|n'
self.assertEqual(health_bar.display_meter(
200, 200, length=40, pre_text="test", post_text="test", align="center"), expected_bar_str)
# test mail contrib

View file

@ -177,7 +177,7 @@ start
#
# This room inherits from a Typeclass called WeatherRoom. It regularly
# and randomly shows some weather effects. Note how we can spread the
# command's arguments over more than one line for easy reading. we
# command's arguments over more than one line for easy reading. We
# also make sure to create plenty of aliases for the room and
# exits. Note the alias tut#02: this unique identifier can be used
# later in the script to always find the way back to this room (for
@ -420,10 +420,6 @@ north
(to get a weapon from the barrel, use |wget weapon|n)
#
@desc barrel =
This barrel has the air of leftovers - it contains an assorted
mess of random weaponry in various states and qualities.
#
@detail barkeep;man;landlord =
The landlord is a cheerful fellow, always ready to supply you with
more beer. He mentions doing some sort of arcane magic known as
@ -439,8 +435,17 @@ north
#
@create/drop barrel: tutorial_world.objects.WeaponRack
#
@desc barrel =
This barrel has the air of leftovers - it contains an assorted
mess of random weaponry in various states and qualities.
#
@lock barrel = get:false()
#
# Players trying to pickup barrel will receive hint to 'get weapon' instead
#
@set barrel/get_err_msg =
The barkeep shakes his head. He says: 'Get weapon, not the barrel.'
#
# This id makes sure that we cannot pick more than one weapon from this rack
#
@set barrel/rack_id = "rack_barrel"
@ -836,7 +841,7 @@ archway
#
# Set its home to this location
#
@home ghost = tut#11
@sethome ghost = tut#11
#
@lock ghost = get:false()
#

View file

@ -37,7 +37,7 @@ class CmdMobOnOff(Command):
to turn on/off the mob."
"""
if not self.args:
self.caller.msg("Usage: mobon|moboff <mob>")
self.caller.msg("Usage: mobon||moboff <mob>")
return
mob = self.caller.search(self.args)
if not mob:

Binary file not shown.

View file

@ -0,0 +1,315 @@
# Polish localization for the EVENNIA.
# Copyright (C) 2019
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <akroczyk@gmail.com>, 2019.
#
msgid ""
msgstr ""
"Project-Id-Version: ArkMUD Polish translation v0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-02-20 12:13+0000\n"
"PO-Revision-Date: 2019-02-20 14:18+0100\n"
"Language: pl_PL\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n"
"%100<12 || n%100>=14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n"
"%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
"Last-Translator: \n"
"Language-Team: ArkMUD team\n"
"X-Generator: Poedit 2.2.1\n"
#: accounts/accounts.py:440
msgid "Account being deleted."
msgstr "Konto zostalo usuniete."
#: commands/cmdhandler.py:681
msgid "There were multiple matches."
msgstr "Znaleziono wiele dopasowan."
#: commands/cmdhandler.py:704
#, python-format
msgid "Command '%s' is not available."
msgstr "Komenda '%s' jests niedostepna."
#: commands/cmdhandler.py:709
#, python-format
msgid " Maybe you meant %s?"
msgstr " Czy miales na mysli %s?"
#: commands/cmdhandler.py:709
msgid "or"
msgstr "lub"
#: commands/cmdhandler.py:711
msgid " Type \"help\" for help."
msgstr " Wpisz \"help\" aby otworzyc pomoc."
#: commands/cmdsethandler.py:89
#, python-brace-format
msgid ""
"{traceback}\n"
"Error loading cmdset '{path}'\n"
"(Traceback was logged {timestamp})"
msgstr ""
#: commands/cmdsethandler.py:94
#, python-brace-format
msgid ""
"Error loading cmdset: No cmdset class '{classname}' in '{path}'.\n"
"(Traceback was logged {timestamp})"
msgstr ""
#: commands/cmdsethandler.py:98
#, python-brace-format
msgid ""
"{traceback}\n"
"SyntaxError encountered when loading cmdset '{path}'.\n"
"(Traceback was logged {timestamp})"
msgstr ""
#: commands/cmdsethandler.py:103
#, python-brace-format
msgid ""
"{traceback}\n"
"Compile/Run error when loading cmdset '{path}'.\",\n"
"(Traceback was logged {timestamp})"
msgstr ""
#: commands/cmdsethandler.py:108
#, python-brace-format
msgid ""
"\n"
"Error encountered for cmdset at path '{path}'.\n"
"Replacing with fallback '{fallback_path}'.\n"
msgstr ""
#: commands/cmdsethandler.py:114
#, python-brace-format
msgid "Fallback path '{fallback_path}' failed to generate a cmdset."
msgstr ""
#: commands/cmdsethandler.py:182 commands/cmdsethandler.py:192
#, python-format
msgid ""
"\n"
"(Unsuccessfully tried '%s')."
msgstr ""
"\n"
"(Bezskuteczna proba '%s')."
#: commands/cmdsethandler.py:311
#, python-brace-format
msgid "custom {mergetype} on cmdset '{cmdset}'"
msgstr ""
#: commands/cmdsethandler.py:314
#, python-brace-format
msgid " <Merged {mergelist} {mergetype}, prio {prio}>: {current}"
msgstr ""
#: commands/cmdsethandler.py:322
#, python-brace-format
msgid ""
" <{key} ({mergetype}, prio {prio}, {permstring})>:\n"
" {keylist}"
msgstr ""
#: commands/cmdsethandler.py:426
msgid "Only CmdSets can be added to the cmdsethandler!"
msgstr ""
#: comms/channelhandler.py:100
msgid "Say what?"
msgstr "Ze co?"
#: comms/channelhandler.py:105
#, python-format
msgid "Channel '%s' not found."
msgstr "Kanal '%s' nie odnaleziony."
#: comms/channelhandler.py:108
#, python-format
msgid "You are not connected to channel '%s'."
msgstr "Nie jestes polaczony z kanalem '%s'."
#: comms/channelhandler.py:112
#, python-format
msgid "You are not permitted to send to channel '%s'."
msgstr "Nie masz uprawnien do wysylania wiadomosci na kanal '%s'."
#: comms/channelhandler.py:155
msgid " (channel)"
msgstr " (kanal)"
#: locks/lockhandler.py:236
#, python-format
msgid "Lock: lock-function '%s' is not available."
msgstr ""
#: locks/lockhandler.py:249
#, python-format
msgid "Lock: definition '%s' has syntax errors."
msgstr ""
#: locks/lockhandler.py:253
#, python-format
msgid ""
"LockHandler on %(obj)s: access type '%(access_type)s' changed from "
"'%(source)s' to '%(goal)s' "
msgstr ""
#: locks/lockhandler.py:320
#, python-brace-format
msgid "Lock: '{lockdef}' contains no colon (:)."
msgstr ""
#: locks/lockhandler.py:328
#, python-brace-format
msgid "Lock: '{lockdef}' has no access_type (left-side of colon is empty)."
msgstr ""
#: locks/lockhandler.py:336
#, python-brace-format
msgid "Lock: '{lockdef}' has mismatched parentheses."
msgstr ""
#: locks/lockhandler.py:343
#, python-brace-format
msgid "Lock: '{lockdef}' has no valid lock functions."
msgstr ""
#: objects/objects.py:732
#, python-format
msgid "Couldn't perform move ('%s'). Contact an admin."
msgstr "Nie udalo sie wykonac ruchu ('%s'). Skontaktuj sie z adminem."
#: objects/objects.py:742
msgid "The destination doesn't exist."
msgstr "Punkt przeznaczenia nie istnieje."
#: objects/objects.py:833
#, python-format
msgid "Could not find default home '(#%d)'."
msgstr "Nie znaleziono domyslnego domu '(#%d)'."
#: objects/objects.py:849
msgid "Something went wrong! You are dumped into nowhere. Contact an admin."
msgstr "Cos poszlo zle! Zostales wrzucony w nicosc. Skontaktuj sie z adminem."
#: objects/objects.py:915
#, python-format
msgid "Your character %s has been destroyed."
msgstr "Twoja postac %s zostala zniszczona."
#: scripts/scripthandler.py:53
#, python-format
msgid ""
"\n"
" '%(key)s' (%(next_repeat)s/%(interval)s, %(repeats)s repeats): %(desc)s"
msgstr ""
#: scripts/scripts.py:205
#, python-format
msgid ""
"Script %(key)s(#%(dbid)s) of type '%(cname)s': at_repeat() error '%(err)s'."
msgstr ""
#: server/initial_setup.py:28
msgid ""
"\n"
"Welcome to your new |wEvennia|n-based game! Visit http://www.evennia.com if "
"you need\n"
"help, want to contribute, report issues or just join the community.\n"
"As Account #1 you can create a demo/tutorial area with |w@batchcommand "
"tutorial_world.build|n.\n"
" "
msgstr ""
"\n"
"Witaj w swojej nowej grze, bazujacej na |wEvennia|n! Odwiedz http://www."
"evennia.com\n"
"jesli potrzebujesz pomocy, chcesz pomoc badz zglosic blad, lub po prostu "
"chcesz dolaczyc do spolecznosci.\n"
"Jako Konto #1 mozesz otworzyc demo/samouczek wpisujac |w@batchcommand "
"tutorial_world.build|n.\n"
" "
#: server/initial_setup.py:92
msgid "This is User #1."
msgstr "To jest User #1."
#: server/initial_setup.py:105
msgid "Limbo"
msgstr "Otchlan"
#: server/server.py:139
msgid "idle timeout exceeded"
msgstr "czas bezczynnosci przekroczony"
#: server/sessionhandler.py:386
msgid " ... Server restarted."
msgstr " ... Serwer zrestartowany."
#: server/sessionhandler.py:606
msgid "Logged in from elsewhere. Disconnecting."
msgstr "Zalogowano z innego miejsca. Rozlaczanie."
#: server/sessionhandler.py:634
msgid "Idle timeout exceeded, disconnecting."
msgstr "Czas bezczynnosci przekroczony, rozlaczanie."
#: server/validators.py:50
#, python-format
msgid ""
"%s From a terminal client, you can also use a phrase of multiple words if "
"you enclose the password in double quotes."
msgstr ""
"%s Z poziomu terminala, mozesz rowniez uzyc frazy z wieloma slowami jesli "
"ujmiesz haslo w cudzyslowie."
#: utils/evmenu.py:192
#, python-brace-format
msgid ""
"Menu node '{nodename}' is either not implemented or caused an error. Make "
"another choice."
msgstr ""
#: utils/evmenu.py:194
#, python-brace-format
msgid "Error in menu node '{nodename}'."
msgstr ""
#: utils/evmenu.py:195
msgid "No description."
msgstr "Brak opisu."
#: utils/evmenu.py:196
msgid "Commands: <menu option>, help, quit"
msgstr ""
#: utils/evmenu.py:197
msgid "Commands: <menu option>, help"
msgstr ""
#: utils/evmenu.py:198
msgid "Commands: help, quit"
msgstr ""
#: utils/evmenu.py:199
msgid "Commands: help"
msgstr ""
#: utils/evmenu.py:200
msgid "Choose an option or try 'help'."
msgstr "Wybierz opcje lub uzyj komendy 'help'."
#: utils/utils.py:1866
#, python-format
msgid "Could not find '%s'."
msgstr "Nie odnaleziono '%s'."
#: utils/utils.py:1873
#, python-format
msgid "More than one match for '%s' (please narrow target):\n"
msgstr "Wiecej niz jedno dopasowanie dla '%s' (prosze zawezyc cel):\n"

View file

@ -69,7 +69,7 @@ class ObjectDBManager(TypedObjectManager):
object candidates.
Return:
match (Object or list): One or more matching results.
match (query): Matching query.
"""
ostring = str(ostring).lstrip('*')
@ -77,7 +77,7 @@ class ObjectDBManager(TypedObjectManager):
dbref = self.dbref(ostring)
if dbref:
try:
return self.get(id=dbref)
return self.get(db_account__id=dbref)
except self.model.DoesNotExist:
pass
@ -87,13 +87,13 @@ class ObjectDBManager(TypedObjectManager):
if exact:
return self.filter(cand_restriction & Q(db_account__username__iexact=ostring))
else: # fuzzy matching
ply_cands = self.filter(cand_restriction & Q(accountdb__username__istartswith=ostring)
).values_list("db_key", flat=True)
if candidates:
index_matches = string_partial_matching(ply_cands, ostring, ret_index=True)
return [obj for ind, obj in enumerate(make_iter(candidates)) if ind in index_matches]
else:
return string_partial_matching(ply_cands, ostring, ret_index=False)
obj_cands = self.select_related().filter(cand_restriction & Q(db_account__username__istartswith=ostring))
acct_cands = [obj.account for obj in obj_cands]
if obj_cands:
index_matches = string_partial_matching([acct.key for acct in acct_cands], ostring, ret_index=True)
acct_cands = [acct_cands[i].id for i in index_matches]
return obj_cands.filter(db_account__id__in=acct_cands)
def get_objs_with_key_and_typeclass(self, oname, otypeclass_path, candidates=None):
"""
@ -105,7 +105,7 @@ class ObjectDBManager(TypedObjectManager):
candidates (list, optional): Only match among the given list of candidates.
Returns:
matches (list): The matching objects.
matches (query): The matching objects.
"""
cand_restriction = candidates is not None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates)
if obj]) or Q()
@ -119,18 +119,19 @@ class ObjectDBManager(TypedObjectManager):
Args:
attribute_name (str): Attribute name to search for.
candidates (list, optional): Only match among the given list of candidates.
candidates (list, optional): Only match among the given list of object
candidates.
Returns:
matches (list): All objects having the given attribute_name defined at all.
matches (query): All objects having the given attribute_name defined at all.
"""
cand_restriction = candidates is not None and Q(db_attributes__db_obj__pk__in=[_GA(obj, "id") for obj
in make_iter(candidates)
if obj]) or Q()
return list(self.filter(cand_restriction & Q(db_attributes__db_key=attribute_name)))
cand_restriction = \
candidates is not None and Q(id__in=[obj.id for obj in candidates]) or Q()
return self.filter(cand_restriction & Q(db_attributes__db_key=attribute_name))
def get_objs_with_attr_value(self, attribute_name, attribute_value, candidates=None, typeclasses=None):
def get_objs_with_attr_value(self, attribute_name, attribute_value,
candidates=None, typeclasses=None):
"""
Get all objects having the given attrname set to the given value.
@ -141,7 +142,8 @@ class ObjectDBManager(TypedObjectManager):
typeclasses (list, optional): Python pats to restrict matches with.
Returns:
matches (list): Objects fullfilling both the `attribute_name` and `attribute_value` criterions.
matches (list): Objects fullfilling both the `attribute_name` and
`attribute_value` criterions.
Notes:
This uses the Attribute's PickledField to transparently search the database by matching

View file

@ -262,7 +262,13 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
con = self.contents_cache.get(exclude=exclude)
# print "contents_get:", self, con, id(self), calledby() # DEBUG
return con
contents = property(contents_get)
def contents_set(self, *args):
"You cannot replace this property"
raise AttributeError("{}.contents is read-only. Use obj.move_to or "
"obj.location to move an object here.".format(self.__class__))
contents = property(contents_get, contents_set, contents_set)
@property
def exits(self):

View file

@ -1,5 +1,6 @@
from evennia.utils.test_resources import EvenniaTest
from evennia import DefaultObject, DefaultCharacter, DefaultRoom, DefaultExit
from evennia.objects.models import ObjectDB
class DefaultObjectTest(EvenniaTest):
@ -45,3 +46,48 @@ class DefaultObjectTest(EvenniaTest):
self.assertTrue(self.room1.get_absolute_url())
self.assertTrue('admin' in self.room1.web_get_admin_url())
class TestObjectManager(EvenniaTest):
"Test object manager methods"
def test_get_object_with_account(self):
query = ObjectDB.objects.get_object_with_account("TestAccount").first()
self.assertEqual(query, self.char1)
query = ObjectDB.objects.get_object_with_account(self.account.dbref)
self.assertEqual(query, self.char1)
query = ObjectDB.objects.get_object_with_account("#123456")
self.assertFalse(query)
query = ObjectDB.objects.get_object_with_account("TestAccou").first()
self.assertFalse(query)
query = ObjectDB.objects.get_object_with_account("TestAccou", exact=False)
self.assertEqual(tuple(query), (self.char1, self.char2))
query = ObjectDB.objects.get_object_with_account(
"TestAccou", candidates=[self.char1, self.obj1], exact=False)
self.assertEqual(list(query), [self.char1])
def test_get_objs_with_key_and_typeclass(self):
query = ObjectDB.objects.get_objs_with_key_and_typeclass(
"Char", "evennia.objects.objects.DefaultCharacter")
self.assertEqual(list(query), [self.char1])
query = ObjectDB.objects.get_objs_with_key_and_typeclass(
"Char", "evennia.objects.objects.DefaultObject")
self.assertFalse(query)
query = ObjectDB.objects.get_objs_with_key_and_typeclass(
"NotFound", "evennia.objects.objects.DefaultCharacter")
self.assertFalse(query)
query = ObjectDB.objects.get_objs_with_key_and_typeclass(
"Char", "evennia.objects.objects.DefaultCharacter", candidates=[self.char1, self.char2])
self.assertEqual(list(query), [self.char1])
def test_get_objs_with_attr(self):
self.obj1.db.testattr = "testval1"
query = ObjectDB.objects.get_objs_with_attr("testattr")
self.assertEqual(list(query), [self.obj1])
query = ObjectDB.objects.get_objs_with_attr(
"testattr", candidates=[self.char1, self.obj1] )
self.assertEqual(list(query), [self.obj1])
query = ObjectDB.objects.get_objs_with_attr(
"NotFound", candidates=[self.char1, self.obj1] )
self.assertFalse(query)

View file

@ -76,7 +76,7 @@ from evennia import prototypes
goblin = {"prototype_key": "goblin:, ... }
prototype = prototypes.save_prototype(caller, **goblin)
prototype = prototypes.save_prototype(goblin)
```

View file

@ -2139,7 +2139,7 @@ def node_prototype_save(caller, **kwargs):
# we already validated and accepted the save, so this node acts as a goto callback and
# should now only return the next node
prototype_key = prototype.get("prototype_key")
protlib.save_prototype(**prototype)
protlib.save_prototype(prototype)
spawned_objects = protlib.search_objects_with_prototype(prototype_key)
nspawned = spawned_objects.count()

View file

@ -333,7 +333,7 @@ def objlist(*args, **kwargs):
def dbref(*args, **kwargs):
"""
Usage $dbref(<#dbref>)
Returns one Object searched globally by #dbref. Error if #dbref is invalid.
Validate that a #dbref input is valid.
"""
if not args or len(args) < 1 or _RE_DBREF.match(args[0]) is None:
raise ValueError('$dbref requires a valid #dbref argument.')

View file

@ -147,13 +147,13 @@ class DbPrototype(DefaultScript):
# Prototype manager functions
def save_prototype(**kwargs):
def save_prototype(prototype):
"""
Create/Store a prototype persistently.
Kwargs:
prototype_key (str): This is required for any storage.
All other kwargs are considered part of the new prototype dict.
Args:
prototype (dict): The prototype to save. A `prototype_key` key is
required.
Returns:
prototype (dict or None): The prototype stored using the given kwargs, None if deleting.
@ -166,8 +166,8 @@ def save_prototype(**kwargs):
is expected to have valid permissions.
"""
kwargs = homogenize_prototype(kwargs)
in_prototype = prototype
in_prototype = homogenize_prototype(in_prototype)
def _to_batchtuple(inp, *args):
"build tuple suitable for batch-creation"
@ -176,7 +176,7 @@ def save_prototype(**kwargs):
return inp
return (inp, ) + args
prototype_key = kwargs.get("prototype_key")
prototype_key = in_prototype.get("prototype_key")
if not prototype_key:
raise ValidationError("Prototype requires a prototype_key")
@ -192,21 +192,21 @@ def save_prototype(**kwargs):
stored_prototype = DbPrototype.objects.filter(db_key=prototype_key)
prototype = stored_prototype[0].prototype if stored_prototype else {}
kwargs['prototype_desc'] = kwargs.get("prototype_desc", prototype.get("prototype_desc", ""))
prototype_locks = kwargs.get(
in_prototype['prototype_desc'] = in_prototype.get("prototype_desc", prototype.get("prototype_desc", ""))
prototype_locks = in_prototype.get(
"prototype_locks", prototype.get('prototype_locks', "spawn:all();edit:perm(Admin)"))
is_valid, err = validate_lockstring(prototype_locks)
if not is_valid:
raise ValidationError("Lock error: {}".format(err))
kwargs['prototype_locks'] = prototype_locks
in_prototype['prototype_locks'] = prototype_locks
prototype_tags = [
_to_batchtuple(tag, _PROTOTYPE_TAG_META_CATEGORY)
for tag in make_iter(kwargs.get("prototype_tags",
for tag in make_iter(in_prototype.get("prototype_tags",
prototype.get('prototype_tags', [])))]
kwargs["prototype_tags"] = prototype_tags
in_prototype["prototype_tags"] = prototype_tags
prototype.update(kwargs)
prototype.update(in_prototype)
if stored_prototype:
# edit existing prototype
@ -261,19 +261,25 @@ def delete_prototype(prototype_key, caller=None):
return True
def search_prototype(key=None, tags=None):
def search_prototype(key=None, tags=None, require_single=False):
"""
Find prototypes based on key and/or tags, or all prototypes.
Kwargs:
key (str): An exact or partial key to query for.
tags (str or list): Tag key or keys to query for. These
tags (str or list): Tag key or keys to query for. These
will always be applied with the 'db_protototype'
tag category.
require_single (bool): If set, raise KeyError if the result
was not found or if there are multiple matches.
Return:
matches (list): All found prototype dicts. If no keys
or tags are given, all available prototypes will be returned.
matches (list): All found prototype dicts. Empty list if
no match was found. Note that if neither `key` nor `tags`
were given, *all* available prototypes will be returned.
Raises:
KeyError: If `require_single` is True and there are 0 or >1 matches.
Note:
The available prototypes is a combination of those supplied in
@ -329,6 +335,10 @@ def search_prototype(key=None, tags=None):
if mta.get('prototype_key') and mta['prototype_key'] == key]
if filter_matches and len(filter_matches) < nmatches:
matches = filter_matches
nmatches = len(matches)
if nmatches != 1 and require_single:
raise KeyError("Found {} matching prototypes.".format(nmatches))
return matches

View file

@ -23,7 +23,7 @@ prot = {
"attrs": [("weapon", "sword")]
}
prot = prototypes.create_prototype(**prot)
prot = prototypes.create_prototype(prot)
```
@ -662,8 +662,9 @@ def spawn(*prototypes, **kwargs):
Spawn a number of prototyped objects.
Args:
prototypes (dict): Each argument should be a prototype
dictionary.
prototypes (str or dict): Each argument should either be a
prototype_key (will be used to find the prototype) or a full prototype
dictionary. These will be batched-spawned as one object each.
Kwargs:
prototype_modules (str or list): A python-path to a prototype
module, or a list of such paths. These will be used to build
@ -673,8 +674,10 @@ def spawn(*prototypes, **kwargs):
prototype_parents (dict): A dictionary holding a custom
prototype-parent dictionary. Will overload same-named
prototypes from prototype_modules.
return_parents (bool): Only return a dict of the
prototype-parents (no object creation happens)
return_parents (bool): Return a dict of the entire prototype-parent tree
available to this prototype (no object creation happens). This is a
merged result between the globally found protparents and whatever
custom `prototype_parents` are given to this function.
only_validate (bool): Only run validation of prototype/parents
(no object creation) and return the create-kwargs.
@ -684,6 +687,11 @@ def spawn(*prototypes, **kwargs):
`return_parents` is set, instead return dict of prototype parents.
"""
# search string (=prototype_key) from input
prototypes = [protlib.search_prototype(prot, require_single=True)[0]
if isinstance(prot, basestring) else prot
for prot in prototypes]
# get available protparents
protparents = {prot['prototype_key'].lower(): prot for prot in protlib.search_prototype()}

View file

@ -54,7 +54,7 @@ class TestSpawner(EvenniaTest):
self.prot1 = {"prototype_key": "testprototype",
"typeclass": "evennia.objects.objects.DefaultObject"}
def test_spawn(self):
def test_spawn_from_prot(self):
obj1 = spawner.spawn(self.prot1)
# check spawned objects have the right tag
self.assertEqual(list(protlib.search_objects_with_prototype("testprototype")), obj1)
@ -62,6 +62,14 @@ class TestSpawner(EvenniaTest):
_PROTPARENTS["GOBLIN"], _PROTPARENTS["GOBLIN_ARCHWIZARD"],
prototype_parents=_PROTPARENTS)], ['goblin grunt', 'goblin archwizard'])
def test_spawn_from_str(self):
protlib.save_prototype(self.prot1)
obj1 = spawner.spawn(self.prot1['prototype_key'])
self.assertEqual(list(protlib.search_objects_with_prototype("testprototype")), obj1)
self.assertEqual([o.key for o in spawner.spawn(
_PROTPARENTS["GOBLIN"], _PROTPARENTS["GOBLIN_ARCHWIZARD"],
prototype_parents=_PROTPARENTS)], ['goblin grunt', 'goblin archwizard'])
class TestUtils(EvenniaTest):
@ -245,6 +253,7 @@ class TestProtLib(EvenniaTest):
super(TestProtLib, self).setUp()
self.obj1.attributes.add("testattr", "testval")
self.prot = spawner.prototype_from_object(self.obj1)
def test_prototype_to_str(self):
prstr = protlib.prototype_to_str(self.prot)
@ -253,6 +262,22 @@ class TestProtLib(EvenniaTest):
def test_check_permission(self):
pass
def test_save_prototype(self):
result = protlib.save_prototype(self.prot)
self.assertEqual(result, self.prot)
# faulty
self.prot['prototype_key'] = None
self.assertRaises(protlib.ValidationError, protlib.save_prototype, self.prot)
def test_search_prototype(self):
protlib.save_prototype(self.prot)
match = protlib.search_prototype("NotFound")
self.assertFalse(match)
match = protlib.search_prototype()
self.assertTrue(match)
match = protlib.search_prototype(self.prot['prototype_key'])
self.assertEqual(match, [self.prot])
@override_settings(PROT_FUNC_MODULES=['evennia.prototypes.protfuncs'], CLIENT_DEFAULT_WIDTH=20)
class TestProtFuncs(EvenniaTest):
@ -424,7 +449,7 @@ class TestPrototypeStorage(EvenniaTest):
def test_prototype_storage(self):
# from evennia import set_trace;set_trace(term_size=(180, 50))
prot1 = protlib.create_prototype(**self.prot1)
prot1 = protlib.create_prototype(self.prot1)
self.assertTrue(bool(prot1))
self.assertEqual(prot1, self.prot1)
@ -436,7 +461,7 @@ class TestPrototypeStorage(EvenniaTest):
protlib.DbPrototype.objects.get_by_tag(
"foo1", _PROTOTYPE_TAG_META_CATEGORY)[0].db.prototype, prot1)
prot2 = protlib.create_prototype(**self.prot2)
prot2 = protlib.create_prototype(self.prot2)
self.assertEqual(
[pobj.db.prototype
for pobj in protlib.DbPrototype.objects.get_by_tag(
@ -445,7 +470,7 @@ class TestPrototypeStorage(EvenniaTest):
# add to existing prototype
prot1b = protlib.create_prototype(
prototype_key='testprototype1', foo='bar', prototype_tags=['foo2'])
{"prototype_key": 'testprototype1', "foo": 'bar', "prototype_tags": ['foo2']})
self.assertEqual(
[pobj.db.prototype
@ -457,7 +482,7 @@ class TestPrototypeStorage(EvenniaTest):
self.assertNotEqual(list(protlib.search_prototype("testprototype1")), [prot1])
self.assertEqual(list(protlib.search_prototype("testprototype1")), [prot1b])
prot3 = protlib.create_prototype(**self.prot3)
prot3 = protlib.create_prototype(self.prot3)
# partial match
with mock.patch("evennia.prototypes.prototypes._MODULE_PROTOTYPES", {}):
@ -608,7 +633,7 @@ class TestMenuModule(EvenniaTest):
self.assertEqual(olc_menus._display_tag(olc_menus._get_menu_prototype(caller)['tags'][0]), Something)
self.assertEqual(olc_menus._caller_tags(caller), ["foo2", "foo3"])
protlib.save_prototype(**self.test_prot)
protlib.save_prototype(self.test_prot)
# locks helpers
self.assertEqual(olc_menus._lock_add(caller, "foo:false()"), "Added lock 'foo:false()'.")

View file

@ -13,6 +13,7 @@ Run the script with the -h flag to see usage information.
import os
import sys
import re
import signal
import shutil
import importlib
@ -25,6 +26,7 @@ from subprocess import Popen, check_output, call, CalledProcessError, STDOUT
from twisted.protocols import amp
from twisted.internet import reactor, endpoints
import django
from django.core.management import execute_from_command_line
# Signal processing
SIG = signal.SIGINT
@ -393,6 +395,7 @@ ERROR_DJANGO_MIN = \
ERROR: Django {dversion} found. Evennia requires version {django_min}
or higher.
TE_TEST
If you are using a virtualenv, use the command `pip install --upgrade -e evennia` where
`evennia` is the folder to where you cloned the Evennia library. If not
in a virtualenv you can install django with for example `pip install --upgrade django`
@ -428,16 +431,15 @@ NOTE_KEYBOARDINTERRUPT = \
NOTE_TEST_DEFAULT = \
"""
TESTING: Using Evennia's default settings file (evennia.settings_default).
(use 'evennia --settings settings.py test .' to run tests on the game dir)
(use 'evennia test --settings settings.py .' to run only your custom game tests)
"""
NOTE_TEST_CUSTOM = \
"""
TESTING: Using specified settings file '{settings_dotpath}'.
(Obs: Evennia's full test suite may not pass if the settings are very
different from the default. Use 'test .' as arguments to run only tests
on the game dir.)
OBS: Evennia's full test suite may not pass if the settings are very
different from the default (use 'evennia test evennia' to run core tests)
"""
PROCESS_ERROR = \
@ -519,7 +521,7 @@ def _print_info(portal_info_dict, server_info_dict):
out = {}
for key, value in dct.items():
if isinstance(value, list):
value = "\n{}".format(ind).join(value)
value = "\n{}".format(ind).join(str(val) for val in value)
out[key] = value
return out
@ -686,13 +688,14 @@ def send_instruction(operation, arguments, callback=None, errback=None):
if AMP_CONNECTION:
# already connected - send right away
_send()
return _send()
else:
# we must connect first, send once connected
point = endpoints.TCP4ClientEndpoint(reactor, AMP_HOST, AMP_PORT)
deferred = endpoints.connectProtocol(point, AMPLauncherProtocol())
deferred.addCallbacks(_on_connect, _on_connect_fail)
REACTOR_RUN = True
return deferred
def query_status(callback=None):
@ -2116,7 +2119,7 @@ def main():
else:
kill(SERVER_PIDFILE, 'Server')
elif option != "noop":
# pass-through to django manager
# pass-through to django manager, but set things up first
check_db = False
need_gamedir = True
# some commands don't require the presence of a game directory to work
@ -2136,31 +2139,16 @@ def main():
init_game_directory(CURRENT_DIR, check_db=check_db, need_gamedir=need_gamedir)
# pass on to the manager
args = [option]
kwargs = {}
if unknown_args:
for arg in unknown_args:
if arg.startswith("--"):
print("arg:", arg)
if "=" in arg:
arg, value = [p.strip() for p in arg.split("=", 1)]
else:
value = True
kwargs[arg.lstrip("--")] = value
else:
args.append(arg)
# makemessages needs a special syntax to not conflict with the -l option
if len(args) > 1 and args[0] == "makemessages":
args.insert(1, "-l")
try:
django.core.management.call_command(*args, **kwargs)
except django.core.management.base.CommandError as exc:
args = ", ".join(args)
kwargs = ", ".join(["--%s" % kw for kw in kwargs])
print(ERROR_INPUT.format(traceback=exc, args=args, kwargs=kwargs))
if option == "migrate":
# we have to launch migrate within the program to make sure migrations
# run within the scope of the launcher (otherwise missing a db will cause errors)
django.core.management.call_command(*([option] + unknown_args))
else:
# pass on to the core django manager - re-parse the entire input line
# but keep 'evennia' as the name instead of django-admin. This is
# an exit condition.
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(execute_from_command_line())
elif not args.tail_log:
# no input; print evennia info (don't pring if we're tailing log)

View file

@ -282,6 +282,8 @@ class AMPMultiConnectionProtocol(amp.AMP):
self.send_mode = True
self.send_task = None
self.multibatches = 0
# later twisted amp has its own __init__
super(AMPMultiConnectionProtocol, self).__init__(*args, **kwargs)
def dataReceived(self, data):
"""

View file

@ -105,7 +105,8 @@ class Portal(object):
sys.path.append('.')
# create a store of services
self.services = service.IServiceCollection(application)
self.services = service.MultiService()
self.services.setServiceParent(application)
self.amp_protocol = None # set by amp factory
self.sessions = PORTAL_SESSIONS
self.sessions.portal = self

View file

@ -103,6 +103,7 @@ class TestMemPlot(TestCase):
@patch.object(memplot, "os")
@patch.object(memplot, "open", new_callable=mock_open, create=True)
@patch.object(memplot, "time")
@patch("evennia.utils.idmapper.models.SharedMemoryModel.flush_from_cache", new=Mock())
def test_memplot(self, mock_time, mocked_open, mocked_os, mocked_idmapper):
if isinstance(memplot, Mock):
return

View file

@ -143,8 +143,6 @@ def _server_maintenance():
session.account.access(session.account, "noidletimeout", default=False):
SESSIONS.disconnect(session, reason=reason)
maintenance_task = LoopingCall(_server_maintenance)
maintenance_task.start(60, now=True) # call every minute
#------------------------------------------------------------
# Evennia Main Server object
@ -199,6 +197,7 @@ class Evennia(object):
reactor.callLater(1, d.callback, None)
reactor.sigInt = _wrap_sigint_handler
# Server startup methods
def sqlite3_prep(self):
@ -303,6 +302,10 @@ class Evennia(object):
"""
from evennia.objects.models import ObjectDB
# start server time and maintenance task
self.maintenance_task = LoopingCall(_server_maintenance)
self.maintenance_task.start(60, now=True) # call every minute
# update eventual changed defaults
self.update_defaults()
@ -322,6 +325,7 @@ class Evennia(object):
# clear eventual lingering session storages
ObjectDB.objects.clear_all_sessids()
logger.log_msg("Evennia Server successfully started.")
# always call this regardless of start type
self.at_server_start()

View file

@ -0,0 +1,126 @@
"""
Test AMP client
"""
import pickle
from unittest import TestCase
from mock import MagicMock, patch
from twisted.trial.unittest import TestCase as TwistedTestCase
from evennia.server import amp_client
from evennia.server.portal import amp_server
from evennia.server.portal import amp
from evennia.server import server
from evennia.server.portal import portal
from evennia.server import serversession, session
from evennia.utils import create
from twisted.internet.base import DelayedCall
DelayedCall.debug = True
class _TestAMP(TwistedTestCase):
def setUp(self):
super(_TestAMP, self).setUp()
with patch("evennia.server.initial_setup.get_god_account") as mockgod:
self.account = create.account("TestAMPAccount", "test@test.com", "testpassword")
mockgod.return_value = self.account
self.server = server.Evennia(MagicMock())
self.server.sessions.data_in = MagicMock()
self.server.sessions.data_out = MagicMock()
self.amp_client_factory = amp_client.AMPClientFactory(self.server)
self.amp_client = self.amp_client_factory.buildProtocol("127.0.0.1")
self.session = MagicMock() # serversession.ServerSession()
self.session.sessid = 1
self.server.sessions[1] = self.session
self.portal = portal.Portal(MagicMock())
self.portalsession = session.Session()
self.portalsession.sessid = 1
self.portal.sessions[1] = self.portalsession
self.portal.sessions.data_in = MagicMock()
self.portal.sessions.data_out = MagicMock()
self.amp_server_factory = amp_server.AMPServerFactory(self.portal)
self.amp_server = self.amp_server_factory.buildProtocol("127.0.0.1")
def tearDown(self):
self.account.delete()
super(_TestAMP, self).tearDown()
def _connect_client(self, mocktransport):
"Setup client to send data for testing"
mocktransport.write = MagicMock()
self.amp_client.makeConnection(mocktransport)
mocktransport.write.reset_mock()
def _connect_server(self, mocktransport):
"Setup server to send data for testing"
mocktransport.write = MagicMock()
self.amp_server.makeConnection(mocktransport)
mocktransport.write.reset_mock()
def _catch_wire_read(self, mocktransport):
"Parse what was supposed to be sent over the wire"
arg_list = mocktransport.write.call_args_list
all_sent = []
for i, cll in enumerate(arg_list):
args, kwargs = cll
raw_inp = args[0]
all_sent.append(raw_inp)
return all_sent
@patch("evennia.server.server.LoopingCall", MagicMock())
@patch("evennia.server.portal.amp.amp.BinaryBoxProtocol.transport")
class TestAMPClientSend(_TestAMP):
"""Test amp client sending data"""
def test_msgserver2portal(self, mocktransport):
self._connect_client(mocktransport)
self.amp_client.send_MsgServer2Portal(self.session, text={"foo": "bar"})
wire_data = self._catch_wire_read(mocktransport)[0]
self._connect_server(mocktransport)
self.amp_server.dataReceived(wire_data)
self.portal.sessions.data_out.assert_called_with(self.portalsession, text={"foo": "bar"})
def test_adminserver2portal(self, mocktransport):
self._connect_client(mocktransport)
self.amp_client.send_AdminServer2Portal(self.session,
operation=amp.PSYNC,
info_dict={}, spid=None)
wire_data = self._catch_wire_read(mocktransport)[0]
self._connect_server(mocktransport)
self.amp_server.data_in = MagicMock()
self.amp_server.dataReceived(wire_data)
self.amp_server.data_in.assert_called()
@patch("evennia.server.portal.amp.amp.BinaryBoxProtocol.transport")
class TestAMPClientRecv(_TestAMP):
"""Test amp client sending data"""
def test_msgportal2server(self, mocktransport):
self._connect_server(mocktransport)
self.amp_server.send_MsgPortal2Server(self.session, text={"foo": "bar"})
wire_data = self._catch_wire_read(mocktransport)[0]
self._connect_client(mocktransport)
self.amp_client.dataReceived(wire_data)
self.server.sessions.data_in.assert_called_with(self.session, text={"foo": "bar"})
def test_adminportal2server(self, mocktransport):
self._connect_server(mocktransport)
self.amp_server.send_AdminPortal2Server(self.session,
operation=amp.PDISCONNALL)
wire_data = self._catch_wire_read(mocktransport)[0]
self._connect_client(mocktransport)
self.server.sessions.portal_disconnect_all = MagicMock()
self.amp_client.dataReceived(wire_data)
self.server.sessions.portal_disconnect_all.assert_called()

View file

@ -0,0 +1,197 @@
"""
Test the evennia launcher.
"""
import os
import pickle
from anything import Something
from mock import patch, MagicMock, create_autospec
from twisted.internet import reactor
from twisted.trial.unittest import TestCase as TwistedTestCase
from evennia.server import evennia_launcher
from evennia.server.portal import amp
from twisted.internet.base import DelayedCall
DelayedCall.debug = True
@patch("evennia.server.evennia_launcher.Popen", new=MagicMock())
class TestLauncher(TwistedTestCase):
def test_is_windows(self):
self.assertEqual(evennia_launcher._is_windows(), os.name == 'nt')
def test_file_compact(self):
self.assertEqual(evennia_launcher._file_names_compact(
"foo/bar/test1", "foo/bar/test2"),
"foo/bar/test1 and test2")
self.assertEqual(evennia_launcher._file_names_compact(
"foo/test1", "foo/bar/test2"),
"foo/test1 and foo/bar/test2")
@patch("evennia.server.evennia_launcher.print")
def test_print_info(self, mockprint):
portal_dict = {
"servername": "testserver",
"version": "1",
"telnet": 1234,
"telnet_ssl": [1234, 2345],
"ssh": 1234,
"webserver_proxy": 1234,
"webclient": 1234,
"webserver_internal": 1234,
"amp": 1234
}
server_dict = {
"servername": "testserver",
"version": "1",
"webserver": [1234, 1234],
"amp": 1234,
"irc_rss": "irc.test",
"info": "testing mode",
"errors": ""
}
evennia_launcher._print_info(portal_dict, server_dict)
mockprint.assert_called()
def test_parse_status(self):
response = {"status": pickle.dumps(("teststring",))}
result = evennia_launcher._parse_status(response)
self.assertEqual(result, ("teststring",))
@patch("evennia.server.evennia_launcher.os.name", new="posix")
def test_get_twisted_cmdline(self):
pcmd, scmd = evennia_launcher._get_twistd_cmdline(False, False)
self.assertTrue("portal.py" in pcmd[1])
self.assertTrue("--pidfile" in pcmd[2])
self.assertTrue("server.py" in scmd[1])
self.assertTrue("--pidfile" in scmd[2])
pcmd, scmd = evennia_launcher._get_twistd_cmdline(True, True)
self.assertTrue("portal.py" in pcmd[1])
self.assertTrue("--pidfile" in pcmd[2])
self.assertTrue("--profiler=cprofile" in pcmd[4], "actual: {}".format(pcmd))
self.assertTrue("--profile=" in pcmd[5])
self.assertTrue("server.py" in scmd[1])
self.assertTrue("--pidfile" in scmd[2])
self.assertTrue("--pidfile" in scmd[2])
self.assertTrue("--profiler=cprofile" in scmd[4], "actual: {}".format(scmd))
self.assertTrue("--profile=" in scmd[5])
@patch("evennia.server.evennia_launcher.os.name", new="nt")
def test_get_twisted_cmdline_nt(self):
pcmd, scmd = evennia_launcher._get_twistd_cmdline(False, False)
self.assertTrue(len(pcmd) == 2, "actual: {}".format(pcmd))
self.assertTrue(len(scmd) == 2, "actual: {}".format(scmd))
@patch("evennia.server.evennia_launcher.reactor.stop")
def test_reactor_stop(self, mockstop):
evennia_launcher._reactor_stop()
mockstop.assert_called()
def _catch_wire_read(self, mocktransport):
"Parse what was supposed to be sent over the wire"
arg_list = mocktransport.write.call_args_list
all_sent = []
for i, cll in enumerate(arg_list):
args, kwargs = cll
raw_inp = args[0]
all_sent.append(raw_inp)
return all_sent
# @patch("evennia.server.portal.amp.amp.BinaryBoxProtocol.transport")
# def test_send_instruction_pstatus(self, mocktransport):
# deferred = evennia_launcher.send_instruction(
# evennia_launcher.PSTATUS,
# (),
# callback=MagicMock(),
# errback=MagicMock())
# on_wire = self._catch_wire_read(mocktransport)
# self.assertEqual(on_wire, "")
# return deferred
def _msend_status_ok(operation, arguments, callback=None, errback=None):
callback({"status": pickle.dumps((True, True, 2, 24, "info1", "info2"))})
def _msend_status_err(operation, arguments, callback=None, errback=None):
errback({"status": pickle.dumps((False, False, 3, 25, "info3", "info4"))})
@patch("evennia.server.evennia_launcher.send_instruction", _msend_status_ok)
@patch("evennia.server.evennia_launcher.NO_REACTOR_STOP", True)
@patch("evennia.server.evennia_launcher.get_pid", MagicMock(return_value=100))
@patch("evennia.server.evennia_launcher.print")
def test_query_status_run(self, mprint):
evennia_launcher.query_status()
mprint.assert_called_with('Portal: RUNNING (pid 100)\nServer: RUNNING (pid 100)')
@patch("evennia.server.evennia_launcher.send_instruction", _msend_status_err)
@patch("evennia.server.evennia_launcher.NO_REACTOR_STOP", True)
@patch("evennia.server.evennia_launcher.print")
def test_query_status_not_run(self, mprint):
evennia_launcher.query_status()
mprint.assert_called_with('Portal: NOT RUNNING\nServer: NOT RUNNING')
@patch("evennia.server.evennia_launcher.send_instruction", _msend_status_ok)
@patch("evennia.server.evennia_launcher.NO_REACTOR_STOP", True)
def test_query_status_callback(self):
mprint = MagicMock()
def testcall(response):
resp = pickle.loads(response['status'])
mprint(resp)
evennia_launcher.query_status(callback=testcall)
mprint.assert_called_with((True, True, 2, 24, "info1", "info2"))
@patch("evennia.server.evennia_launcher.AMP_CONNECTION")
@patch("evennia.server.evennia_launcher.print")
def test_wait_for_status_reply(self, mprint, aconn):
aconn.wait_for_status = MagicMock()
def test():
pass
evennia_launcher.wait_for_status_reply(test)
aconn.wait_for_status.assert_called_with(test)
@patch("evennia.server.evennia_launcher.AMP_CONNECTION", None)
@patch("evennia.server.evennia_launcher.print")
def test_wait_for_status_reply_fail(self, mprint):
evennia_launcher.wait_for_status_reply(None)
mprint.assert_called_with("No Evennia connection established.")
@patch("evennia.server.evennia_launcher.send_instruction", _msend_status_ok)
@patch("evennia.server.evennia_launcher.reactor.callLater")
def test_wait_for_status(self, mcalllater):
mcall = MagicMock()
merr = MagicMock()
evennia_launcher.wait_for_status(
portal_running=True,
server_running=True,
callback=mcall,
errback=merr)
mcall.assert_called_with(True, True)
merr.assert_not_called()
@patch("evennia.server.evennia_launcher.send_instruction", _msend_status_err)
@patch("evennia.server.evennia_launcher.reactor.callLater")
def test_wait_for_status_fail(self, mcalllater):
mcall = MagicMock()
merr = MagicMock()
evennia_launcher.wait_for_status(
portal_running=True,
server_running=True,
callback=mcall,
errback=merr)
mcall.assert_not_called()
merr.assert_not_called()
mcalllater.assert_called()

View file

@ -0,0 +1,242 @@
"""
Test the main server component
"""
from unittest import TestCase
from mock import MagicMock, patch, DEFAULT, call
from django.test import override_settings
from evennia.utils.test_resources import unload_module
@patch("evennia.server.server.LoopingCall", new=MagicMock())
class TestServer(TestCase):
"""
Test server module.
"""
def setUp(self):
from evennia.server import server
self.server = server
def test__server_maintenance_reset(self):
with patch.multiple("evennia.server.server",
LoopingCall=DEFAULT,
Evennia=DEFAULT,
_FLUSH_CACHE=DEFAULT,
connection=DEFAULT,
_IDMAPPER_CACHE_MAXSIZE=1000,
_MAINTENANCE_COUNT=0,
ServerConfig=DEFAULT) as mocks:
mocks['connection'].close = MagicMock()
mocks['ServerConfig'].objects.conf = MagicMock(return_value=456)
# flush cache
self.server._server_maintenance()
mocks['ServerConfig'].objects.conf.assert_called_with('runtime', 456)
def test__server_maintenance_flush(self):
with patch.multiple("evennia.server.server",
LoopingCall=DEFAULT,
Evennia=DEFAULT,
_FLUSH_CACHE=DEFAULT,
connection=DEFAULT,
_IDMAPPER_CACHE_MAXSIZE=1000,
_MAINTENANCE_COUNT=600 - 1,
ServerConfig=DEFAULT) as mocks:
mocks['connection'].close = MagicMock()
mocks['ServerConfig'].objects.conf = MagicMock(return_value=100)
# flush cache
self.server._server_maintenance()
mocks['_FLUSH_CACHE'].assert_called_with(1000)
def test__server_maintenance_validate_scripts(self):
with patch.multiple("evennia.server.server",
LoopingCall=DEFAULT,
Evennia=DEFAULT,
_FLUSH_CACHE=DEFAULT,
connection=DEFAULT,
_IDMAPPER_CACHE_MAXSIZE=1000,
_MAINTENANCE_COUNT=3600 - 1,
ServerConfig=DEFAULT) as mocks:
mocks['connection'].close = MagicMock()
mocks['ServerConfig'].objects.conf = MagicMock(return_value=100)
with patch("evennia.server.server.evennia.ScriptDB.objects.validate") as mock:
self.server._server_maintenance()
mocks['_FLUSH_CACHE'].assert_called_with(1000)
mock.assert_called()
def test__server_maintenance_channel_handler_update(self):
with patch.multiple("evennia.server.server",
LoopingCall=DEFAULT,
Evennia=DEFAULT,
_FLUSH_CACHE=DEFAULT,
connection=DEFAULT,
_IDMAPPER_CACHE_MAXSIZE=1000,
_MAINTENANCE_COUNT=3700 - 1,
ServerConfig=DEFAULT) as mocks:
mocks['connection'].close = MagicMock()
mocks['ServerConfig'].objects.conf = MagicMock(return_value=100)
with patch("evennia.server.server.evennia.CHANNEL_HANDLER.update") as mock:
self.server._server_maintenance()
mock.assert_called()
def test__server_maintenance_close_connection(self):
with patch.multiple("evennia.server.server",
LoopingCall=DEFAULT,
Evennia=DEFAULT,
_FLUSH_CACHE=DEFAULT,
connection=DEFAULT,
_IDMAPPER_CACHE_MAXSIZE=1000,
_MAINTENANCE_COUNT=(3600 * 7) - 1,
ServerConfig=DEFAULT) as mocks:
mocks['connection'].close = MagicMock()
mocks['ServerConfig'].objects.conf = MagicMock(return_value=100)
self.server._server_maintenance()
mocks['connection'].close.assert_called()
def test__server_maintenance_idle_time(self):
with patch.multiple("evennia.server.server",
LoopingCall=DEFAULT,
Evennia=DEFAULT,
_FLUSH_CACHE=DEFAULT,
connection=DEFAULT,
_IDMAPPER_CACHE_MAXSIZE=1000,
_MAINTENANCE_COUNT=(3600 * 7) - 1,
SESSIONS=DEFAULT,
_IDLE_TIMEOUT=10,
time=DEFAULT,
ServerConfig=DEFAULT) as mocks:
sess1 = MagicMock()
sess2 = MagicMock()
sess3 = MagicMock()
sess4 = MagicMock()
sess1.cmd_last = 100 # should time out
sess2.cmd_last = 999 # should not time out
sess3.cmd_last = 100 # should not time (due to account)
sess4.cmd_last = 100 # should time out (due to access)
sess1.account = None
sess2.account = None
sess3.account = MagicMock()
sess3.account = MagicMock()
sess4.account.access = MagicMock(return_value=False)
mocks['time'].time = MagicMock(return_value=1000)
mocks['ServerConfig'].objects.conf = MagicMock(return_value=100)
mocks["SESSIONS"].values = MagicMock(return_value=[sess1, sess2, sess3, sess4])
mocks["SESSIONS"].disconnect = MagicMock()
self.server._server_maintenance()
reason = "idle timeout exceeded"
calls = [call(sess1, reason=reason), call(sess4, reason=reason)]
mocks["SESSIONS"].disconnect.assert_has_calls(calls, any_order=True)
def test_evennia_start(self):
with patch.multiple("evennia.server.server",
time=DEFAULT,
service=DEFAULT) as mocks:
mocks['time'].time = MagicMock(return_value=1000)
evennia = self.server.Evennia(MagicMock())
self.assertEqual(evennia.start_time, 1000)
@patch("evennia.objects.models.ObjectDB")
@patch("evennia.server.server.AccountDB")
@patch("evennia.server.server.ScriptDB")
@patch("evennia.comms.models.ChannelDB")
def test_update_defaults(self, mockchan, mockscript, mockacct, mockobj):
with patch.multiple("evennia.server.server",
ServerConfig=DEFAULT) as mocks:
mockchan.objects.filter = MagicMock()
mockscript.objects.filter = MagicMock()
mockacct.objects.filter = MagicMock()
mockobj.objects.filter = MagicMock()
# fake mismatches
settings_names = ("CMDSET_CHARACTER", "CMDSET_ACCOUNT",
"BASE_ACCOUNT_TYPECLASS", "BASE_OBJECT_TYPECLASS",
"BASE_CHARACTER_TYPECLASS", "BASE_ROOM_TYPECLASS",
"BASE_EXIT_TYPECLASS", "BASE_SCRIPT_TYPECLASS",
"BASE_CHANNEL_TYPECLASS")
fakes = {name: "Dummy.path" for name in settings_names}
def _mock_conf(key, *args):
return fakes[key]
mocks['ServerConfig'].objects.conf = _mock_conf
evennia = self.server.Evennia(MagicMock())
evennia.update_defaults()
mockchan.objects.filter.assert_called()
mockscript.objects.filter.assert_called()
mockacct.objects.filter.assert_called()
mockobj.objects.filter.assert_called()
def test_initial_setup(self):
from evennia.utils.create import create_account
acct = create_account("TestSuperuser", "test@test.com", "testpassword",
is_superuser=True)
with patch.multiple("evennia.server.initial_setup",
reset_server=DEFAULT,
AccountDB=DEFAULT) as mocks:
mocks['AccountDB'].objects.get = MagicMock(return_value=acct)
evennia = self.server.Evennia(MagicMock())
evennia.run_initial_setup()
acct.delete()
def test_initial_setup_retry(self):
from evennia.utils.create import create_account
acct = create_account("TestSuperuser2", "test@test.com", "testpassword",
is_superuser=True)
with patch.multiple("evennia.server.initial_setup",
ServerConfig=DEFAULT,
reset_server=DEFAULT,
AccountDB=DEFAULT) as mocks:
mocks['AccountDB'].objects.get = MagicMock(return_value=acct)
# a last_initial_setup_step > 0
mocks['ServerConfig'].objects.conf = MagicMock(return_value=4)
evennia = self.server.Evennia(MagicMock())
evennia.run_initial_setup()
acct.delete()
@override_settings(DEFAULT_HOME="#1")
def test_run_init_hooks(self):
from evennia.utils import create
obj1 = create.object(key="HookTestObj1")
obj2 = create.object(key="HookTestObj2")
acct1 = create.account("HookAcct1", "hooktest1@test.com", "testpasswd")
acct2 = create.account("HookAcct2", "hooktest2@test.com", "testpasswd")
with patch("evennia.objects.models.ObjectDB") as mockobj:
with patch("evennia.server.server.AccountDB") as mockacct:
mockacct.get_all_cached_instances = MagicMock(return_value=[acct1, acct2])
mockobj.get_all_cached_instances = MagicMock(return_value=[obj1, obj2])
mockobj.objects.clear_all_sessids = MagicMock()
evennia = self.server.Evennia(MagicMock())
evennia.run_init_hooks('reload')
evennia.run_init_hooks('reset')
evennia.run_init_hooks('shutdown')
mockacct.get_all_cached_instances.assert_called()
mockobj.get_all_cached_instances.assert_called()
mockobj.objects.clear_all_sessids.assert_called_with()
obj1.delete()
obj2.delete()
acct1.delete()
acct2.delete()
@patch('evennia.server.server.INFO_DICT', {"test": "foo"})
def test_get_info_dict(self):
evennia = self.server.Evennia(MagicMock())
self.assertEqual(evennia.get_info_dict(), {"test": "foo"})

View file

@ -383,7 +383,9 @@ class AttributeHandler(object):
"""
ret = []
category = category.strip().lower() if category is not None else None
for keystr in make_iter(key):
keystr = key.strip().lower()
ret.extend(bool(attr) for attr in self._getcache(keystr, category))
return ret[0] if len(ret) == 1 else ret
@ -605,7 +607,8 @@ class AttributeHandler(object):
Remove attribute or a list of attributes from object.
Args:
key (str): An Attribute key to remove.
key (str or list): An Attribute key to remove or a list of keys. If
multiple keys, they must all be of the same `category`.
raise_exception (bool, optional): If set, not finding the
Attribute to delete will raise an exception instead of
just quietly failing.
@ -623,7 +626,11 @@ class AttributeHandler(object):
was found matching `key`.
"""
category = category.strip().lower() if category is not None else None
for keystr in make_iter(key):
keystr = keystr.lower()
attr_objs = self._getcache(keystr, category)
for attr_obj in attr_objs:
if not (
@ -634,10 +641,11 @@ class AttributeHandler(object):
try:
attr_obj.delete()
except AssertionError:
print("Assertionerror for attr.delete()")
# this happens if the attr was already deleted
pass
finally:
self._delcache(key, category)
self._delcache(keystr, category)
if not attr_objs and raise_exception:
raise AttributeError
@ -655,6 +663,8 @@ class AttributeHandler(object):
type `attredit` on the Attribute in question.
"""
category = category.strip().lower() if category is not None else None
if not self._cache_complete:
self._fullcache()
if accessing_obj:

View file

@ -402,6 +402,7 @@ def create_account(key, email, password,
typeclass=None,
is_superuser=False,
locks=None, permissions=None,
tags=None, attributes=None,
report_to=None):
"""
This creates a new account.
@ -414,15 +415,20 @@ def create_account(key, email, password,
password (str): Password in cleartext.
Kwargs:
typeclass (str): The typeclass to use for the account.
is_superuser (bool): Wether or not this account is to be a superuser
locks (str): Lockstring.
permission (list): List of permission strings.
tags (list): List of Tags on form `(key, category[, data])`
attributes (list): List of Attributes on form
`(key, value [, category, [,lockstring [, default_pass]]])`
report_to (Object): An object with a msg() method to report
errors to. If not given, errors will be logged.
Raises:
ValueError: If `key` already exists in database.
Notes:
Usually only the server admin should need to be superuser, all
other access levels can be handled with more fine-grained
@ -437,6 +443,8 @@ def create_account(key, email, password,
typeclass = typeclass if typeclass else settings.BASE_ACCOUNT_TYPECLASS
locks = make_iter(locks) if locks is not None else None
permissions = make_iter(permissions) if permissions is not None else None
tags = make_iter(tags) if tags is not None else None
attributes = make_iter(attributes) if attributes is not None else None
if isinstance(typeclass, str):
# a path is given. Load the actual typeclass.
@ -462,7 +470,8 @@ def create_account(key, email, password,
is_staff=is_superuser, is_superuser=is_superuser,
last_login=now, date_joined=now)
new_account.set_password(password)
new_account._createdict = dict(locks=locks, permissions=permissions, report_to=report_to)
new_account._createdict = dict(locks=locks, permissions=permissions, report_to=report_to,
tags=tags, attributes=attributes)
# saving will trigger the signal that calls the
# at_first_save hook on the typeclass, where the _createdict
# can be used.

View file

@ -510,7 +510,7 @@ class CmdEditorGroup(CmdEditorBase):
else:
buf = linebuffer[:lstart] + editor._copy_buffer + linebuffer[lstart:]
editor.update_buffer(buf)
caller.msg("Copied buffer %s to %s." % (editor._copy_buffer, self.lstr))
caller.msg("Pasted buffer %s to %s." % (editor._copy_buffer, self.lstr))
elif cmd == ":i":
# :i <l> <txt> - insert new line
new_lines = self.args.split('\n')

View file

@ -1,3 +1,8 @@
"""
Various helper resources for writing unittests.
"""
import sys
from django.conf import settings
from django.test import TestCase
from mock import Mock
@ -10,8 +15,38 @@ from evennia.utils import create
from evennia.utils.idmapper.models import flush_cache
SESSIONS.data_out = Mock()
SESSIONS.disconnect = Mock()
def unload_module(module):
"""
Reset import so one can mock global constants.
Args:
module (module, object or str): The module will
be removed so it will have to be imported again. If given
an object, the module in which that object sits will be unloaded. A string
should directly give the module pathname to unload.
Example:
# (in a test method)
unload_module(foo)
with mock.patch("foo.GLOBALTHING", "mockval"):
import foo
... # test code using foo.GLOBALTHING, now set to 'mockval'
This allows for mocking constants global to the module, since
otherwise those would not be mocked (since a module is only
loaded once).
"""
if isinstance(module, basestring):
modulename = module
elif hasattr(module, "__module__"):
modulename = module.__module__
else:
modulename = module.__name__
if modulename in sys.modules:
del sys.modules[modulename]
class EvenniaTest(TestCase):
@ -29,6 +64,11 @@ class EvenniaTest(TestCase):
"""
Sets up testing environment
"""
self.backups = (SESSIONS.data_out, SESSIONS.disconnect,
settings.DEFAULT_HOME, settings.PROTOTYPE_MODULES)
SESSIONS.data_out = Mock()
SESSIONS.disconnect = Mock()
self.account = create.create_account("TestAccount", email="test@test.com", password="testpassword", typeclass=self.account_typeclass)
self.account2 = create.create_account("TestAccount2", email="test@test.com", password="testpassword", typeclass=self.account_typeclass)
self.room1 = create.create_object(self.room_typeclass, key="Room", nohome=True)
@ -62,6 +102,11 @@ class EvenniaTest(TestCase):
def tearDown(self):
flush_cache()
SESSIONS.data_out = self.backups[0]
SESSIONS.disconnect = self.backups[1]
settings.DEFAULT_HOME = self.backups[2]
settings.PROTOTYPE_MODULES = self.backups[3]
del SESSIONS[self.session.sessid]
self.account.delete()
self.account2.delete()

View file

@ -0,0 +1,93 @@
"""
Test eveditor
"""
from mock import MagicMock
from django.test import TestCase
from evennia.utils import eveditor
from evennia.commands.default.tests import CommandTest
class TestEvEditor(CommandTest):
def test_eveditor_view_cmd(self):
eveditor.EvEditor(self.char1)
self.call(eveditor.CmdEditorGroup(), "", cmdstring=":h",
msg="<txt> - any non-command is appended to the end of the buffer.")
# empty buffer
self.call(eveditor.CmdEditorGroup(), "", cmdstring=":",
msg="Line Editor []\n01\n[l:01 w:000 c:0000](:h for help)")
# input a string
self.call(eveditor.CmdLineInput(), "First test line", raw_string="First test line",
msg="01First test line")
self.call(eveditor.CmdLineInput(), "Second test line", raw_string="Second test line",
msg="02Second test line")
self.assertEqual(self.char1.ndb._eveditor.get_buffer(), 'First test line\nSecond test line')
self.call(eveditor.CmdEditorGroup(), "", cmdstring=":", # view buffer
msg="Line Editor []\n01First test line\n"
"02Second test line\n[l:02 w:006 c:0032](:h for help)")
self.call(eveditor.CmdEditorGroup(), "", cmdstring="::", # view buffer, no linenums
msg="Line Editor []\nFirst test line\n"
"Second test line\n[l:02 w:006 c:0032](:h for help)")
self.call(eveditor.CmdEditorGroup(), "", cmdstring=":::", # add single : alone on row
msg="Single ':' added to buffer.")
self.call(eveditor.CmdEditorGroup(), "", cmdstring=":",
msg="Line Editor []\n01First test line\n"
"02Second test line\n03:\n[l:03 w:007 c:0034](:h for help)")
self.call(eveditor.CmdEditorGroup(), "", cmdstring=":dd", # delete line
msg="Deleted line 3.")
self.assertEqual(self.char1.ndb._eveditor.get_buffer(),
'First test line\nSecond test line')
self.call(eveditor.CmdEditorGroup(), "", cmdstring=":u", # undo
msg="Undid one step.")
self.assertEqual(self.char1.ndb._eveditor.get_buffer(),
'First test line\nSecond test line\n:')
self.call(eveditor.CmdEditorGroup(), "", cmdstring=":uu", # redo
msg="Redid one step.")
self.assertEqual(self.char1.ndb._eveditor.get_buffer(),
'First test line\nSecond test line')
self.call(eveditor.CmdEditorGroup(), "", cmdstring=":u", # undo
msg="Undid one step.")
self.assertEqual(self.char1.ndb._eveditor.get_buffer(),
'First test line\nSecond test line\n:')
self.call(eveditor.CmdEditorGroup(), "", cmdstring=":",
msg="Line Editor []\n01First test line\n"
"02Second test line\n03:\n[l:03 w:007 c:0034](:h for help)")
self.call(eveditor.CmdEditorGroup(), "Second", cmdstring=":dw", # delete by word
msg="Removed Second for lines 1-4.")
self.call(eveditor.CmdEditorGroup(), "", cmdstring=":u", # undo
msg="Undid one step.")
self.call(eveditor.CmdEditorGroup(), "2 Second", cmdstring=":dw", # delete by word/line
msg="Removed Second for line 2.")
self.assertEqual(self.char1.ndb._eveditor.get_buffer(),
'First test line\n test line\n:')
self.call(eveditor.CmdEditorGroup(), "2", cmdstring=":p", # paste
msg="Copy buffer is empty.")
self.call(eveditor.CmdEditorGroup(), "2", cmdstring=":y", # yank
msg="Line 2, [' test line'] yanked.")
self.call(eveditor.CmdEditorGroup(), "2", cmdstring=":p", # paste
msg="Pasted buffer [' test line'] to line 2.")
self.assertEqual(self.char1.ndb._eveditor.get_buffer(),
'First test line\n test line\n test line\n:')
self.call(eveditor.CmdEditorGroup(), "3", cmdstring=":x", # cut
msg="Line 3, [' test line'] cut.")
self.call(eveditor.CmdEditorGroup(), "2 New Second line", cmdstring=":i", # insert
msg="Inserted 1 new line(s) at line 2.")
self.call(eveditor.CmdEditorGroup(), "2 New Replaced Second line", # replace
cmdstring=":r", msg="Replaced 1 line(s) at line 2.")
self.call(eveditor.CmdEditorGroup(), "2 Inserted-", # insert beginning line
cmdstring=":I", msg="Inserted text at beginning of line 2.")
self.call(eveditor.CmdEditorGroup(), "2 -End", # append end line
cmdstring=":A", msg="Appended text to end of line 2.")
self.assertEqual(
self.char1.ndb._eveditor.get_buffer(),
'First test line\nInserted-New Replaced Second line-End\n test line\n:')

View file

@ -215,6 +215,7 @@ class TextToHTMLparser(object):
text (str): Processed text.
"""
return text
return text.replace(r'\n', r'<br>')
def convert_urls(self, text):

View file

@ -1,4 +1,4 @@
{% extends "base.html" %}
{% extends "website/base.html" %}
{% block titleblock %}404 - Not Found{% endblock %}
@ -10,7 +10,7 @@
<div class="row">
<div class="col">
<div class="card">
<div class="card-block">
<div class="card-body">
<h1 class="card-title">Error 404</h1>
<h4 class="card-subtitle">Page not found</h2>
<hr />

View file

@ -1,4 +1,4 @@
{% extends "base.html" %}
{% extends "website/base.html" %}
{% block titleblock %}500 - Internal Server Error{% endblock %}
@ -10,7 +10,7 @@
<div class="row">
<div class="col">
<div class="card">
<div class="card-block">
<div class="card-body">
<h1 class="card-title">Error 500</h1>
<h4 class="card-subtitle">Internal Server Error</h2>
<hr />

View file

@ -1,4 +1,4 @@
{% extends "base.html" %}
{% extends "website/base.html" %}
{% block titleblock %}Admin{% endblock %}
@ -6,7 +6,7 @@
<div class="row">
<div class="col">
<div class="card">
<div class="card-block p-4">
<div class="card-body">
<h1 class="card-title">Admin</h1>
<p class="card-text">
Welcome to the Evennia Admin Page. Here, you can edit many facets of accounts, characters, and other parts of the game.

View file

@ -1,4 +1,4 @@
{% extends "base.html" %}
{% extends "website/base.html" %}
{% block header_ext %}
{% endblock %}
@ -7,7 +7,7 @@
<div class="row">
<div class="col">
<div class="card">
<div class="card-block">
<div class="card-body">
<h1 class="card-title">{{flatpage.title}}</h1>
{{flatpage.content}}
</div>

View file

@ -1,4 +1,4 @@
{% extends "base.html" %}
{% extends "website/base.html" %}
{% block titleblock %}Home{% endblock %}

View file

@ -1,4 +1,4 @@
{% extends "base.html" %}
{% extends "website/base.html" %}
{% block header_ext %}
<META NAME="ROBOTS" CONTENT="NOINDEX, NOFOLLOW">

View file

@ -1,4 +1,4 @@
{% extends "base.html" %}
{% extends "website/base.html" %}
{% block titleblock %}
Login

View file

@ -1,4 +1,4 @@
{% extends "base.html" %}
{% extends "website/base.html" %}
{% block titleblock %}To Be Implemented{% endblock %}
@ -12,7 +12,7 @@
<div class="col">
<div class="card text-center">
<h1 class="card-header">To Be Implemented...</h1>
<div class="card-block">
<div class="card-body">
This feature has yet to be implemented, but rest assured that it will be!
<br />
<br />