"""
Various helper resources for writing unittests.
"""
import sys
from twisted.internet.defer import Deferred
from django.conf import settings
from django.test import TestCase, override_settings
from mock import Mock, patch
from evennia.objects.objects import DefaultObject, DefaultCharacter, DefaultRoom, DefaultExit
from evennia.accounts.accounts import DefaultAccount
from evennia.scripts.scripts import DefaultScript
from evennia.server.serversession import ServerSession
from evennia.server.sessionhandler import SESSIONS
from evennia.utils import create
from evennia.utils.idmapper.models import flush_cache
from evennia.utils.utils import all_from_module
from evennia import settings_default
# set up a 'pristine' setting, unaffected by any changes in mygame
DEFAULT_SETTING_RESETS = dict(
CONNECTION_SCREEN_MODULE="evennia.game_template.server.conf.connection_screens",
AT_SERVER_STARTSTOP_MODULE="evennia.game_template.server.conf.at_server_startstop",
AT_SERVICES_PLUGINS_MODULES=["evennia.game_template.server.conf.server_services_plugins"],
PORTAL_SERVICES_PLUGIN_MODULES=["evennia.game_template.server.conf.portal_services_plugins"],
MSSP_META_MODULE="evennia.game_template.server.conf.mssp",
WEB_PLUGINS_MODULE="server.conf.web_plugins",
LOCK_FUNC_MODULES=("evennia.locks.lockfuncs", "evennia.game_template.server.conf.lockfuncs"),
INPUT_FUNC_MODULES=["evennia.server.inputfuncs",
"evennia.game_template.server.conf.inputfuncs"],
PROTOTYPE_MODULES=["evennia.game_template.world.prototypes"],
CMDSET_UNLOGGEDIN="evennia.game_template.commands.default_cmdsets.UnloggedinCmdSet",
CMDSET_SESSION="evennia.game_template.commands.default_cmdsets.SessionCmdSet",
CMDSET_CHARACTER="evennia.game_template.commands.default_cmdsets.CharacterCmdSet",
CMDSET_ACCOUNT="evennia.game_template.commands.default_cmdsets.AccountCmdSet",
CMDSET_PATHS=["evennia.game_template.commands", "evennia", "evennia.contrib"],
TYPECLASS_PATHS=[
"evennia",
"evennia.contrib",
"evennia.contrib.game_systems",
"evennia.contrib.base_systems",
"evennia.contrib.full_systems",
"evennia.contrib.tutorials",
"evennia.contrib.utils"],
BASE_ACCOUNT_TYPECLASS="evennia.accounts.accounts.DefaultAccount",
BASE_OBJECT_TYPECLASS="evennia.objects.objects.DefaultObject",
BASE_CHARACTER_TYPECLASS="evennia.objects.objects.DefaultCharacter",
BASE_ROOM_TYPECLASS="evennia.objects.objects.DefaultRoom",
BASE_EXIT_TYPECLASS="evennia.objects.objects.DefaultExit",
BASE_CHANNEL_TYPECLASS="evennia.comms.comms.DefaultChannel",
BASE_SCRIPT_TYPECLASS="evennia.scripts.scripts.DefaultScript",
BASE_BATCHPROCESS_PATHS=["evennia.game_template.world",
"evennia.contrib", "evennia.contrib.tutorials"],
FILE_HELP_ENTRY_MODULES=["evennia.game_template.world.help_entries"],
FUNCPARSER_OUTGOING_MESSAGES_MODULES=["evennia.utils.funcparser",
"evennia.game_template.server.conf.inlinefuncs"],
FUNCPARSER_PROTOTYPE_PARSING_MODULES=["evennia.prototypes.protfuncs",
"evennia.game_template.server.conf.prototypefuncs"],
BASE_GUEST_TYPECLASS="evennia.accounts.accounts.DefaultGuest",
)
DEFAULT_SETTINGS = {
**all_from_module(settings_default),
**DEFAULT_SETTING_RESETS
}
DEFAULT_SETTINGS.pop("DATABASES") # we want different dbs tested in CI
# mocking of evennia.utils.utils.delay
[docs]def mockdelay(timedelay, callback, *args, **kwargs):
callback(*args, **kwargs)
return Deferred()
# mocking of twisted's deferLater
[docs]def mockdeferLater(reactor, timedelay, callback, *args, **kwargs):
callback(*args, **kwargs)
return Deferred()
[docs]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:
```python
# (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, str):
modulename = module
elif hasattr(module, "__module__"):
modulename = module.__module__
else:
modulename = module.__name__
if modulename in sys.modules:
del sys.modules[modulename]
def _mock_deferlater(reactor, timedelay, callback, *args, **kwargs):
callback(*args, **kwargs)
return Deferred()
[docs]class EvenniaTestMixin:
"""
Evennia test environment mixin
"""
account_typeclass = DefaultAccount
object_typeclass = DefaultObject
character_typeclass = DefaultCharacter
exit_typeclass = DefaultExit
room_typeclass = DefaultRoom
script_typeclass = DefaultScript
[docs] def create_accounts(self):
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.account.permissions.add("Developer")
[docs] def teardown_accounts(self):
if hasattr(self, "account"):
self.account.delete()
if hasattr(self, "account2"):
self.account2.delete()
[docs] def create_rooms(self):
self.room1 = create.create_object(self.room_typeclass, key="Room", nohome=True)
self.room1.db.desc = "room_desc"
settings.DEFAULT_HOME = "#%i" % self.room1.id # we must have a default home
# Set up fake prototype module for allowing tests to use named prototypes.
settings.PROTOTYPE_MODULES = "evennia.utils.tests.data.prototypes_example"
self.room2 = create.create_object(self.room_typeclass, key="Room2")
self.exit = create.create_object(
self.exit_typeclass, key="out", location=self.room1, destination=self.room2
)
[docs] def create_objs(self):
self.obj1 = create.create_object(
self.object_typeclass, key="Obj", location=self.room1, home=self.room1
)
self.obj2 = create.create_object(
self.object_typeclass, key="Obj2", location=self.room1, home=self.room1
)
[docs] def create_chars(self):
self.char1 = create.create_object(
self.character_typeclass, key="Char", location=self.room1, home=self.room1
)
self.char1.permissions.add("Developer")
self.char2 = create.create_object(
self.character_typeclass, key="Char2", location=self.room1, home=self.room1
)
self.char1.account = self.account
self.account.db._last_puppet = self.char1
self.char2.account = self.account2
self.account2.db._last_puppet = self.char2
[docs] def create_script(self):
self.script = create.create_script(self.script_typeclass, key="Script")
[docs] def setup_session(self):
dummysession = ServerSession()
dummysession.init_session("telnet", ("localhost", "testmode"), SESSIONS)
dummysession.sessid = 1
SESSIONS.portal_connect(
dummysession.get_sync_data()
) # note that this creates a new Session!
session = SESSIONS.session_from_sessid(1) # the real session
SESSIONS.login(session, self.account, testmode=True)
self.session = session
[docs] def teardown_session(self):
if hasattr(self, "sessions"):
del SESSIONS[self.session.sessid]
[docs] @patch("evennia.scripts.taskhandler.deferLater", _mock_deferlater)
def setUp(self):
"""
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.create_accounts()
self.create_rooms()
self.create_objs()
self.create_chars()
self.create_script()
self.setup_session()
[docs] def tearDown(self):
flush_cache()
try:
SESSIONS.data_out = self.backups[0]
SESSIONS.disconnect = self.backups[1]
settings.DEFAULT_HOME = self.backups[2]
settings.PROTOTYPE_MODULES = self.backups[3]
except AttributeError as err:
raise AttributeError(f"{err}: Teardown error. If you overrode the `setUp()` method "
"in your test, make sure you also added `super().setUp()`!")
del SESSIONS[self.session.sessid]
self.teardown_accounts()
super().tearDown()
[docs]@override_settings(**DEFAULT_SETTINGS)
class BaseEvenniaTestCase(TestCase):
"""
Base test (with no default objects) but with
enforced default settings.
"""
[docs]@override_settings(**DEFAULT_SETTINGS)
class BaseEvenniaTest(EvenniaTestMixin, TestCase):
"""
This class parent has all default objects and uses only default settings.
"""
[docs]class EvenniaTest(EvenniaTestMixin, TestCase):
"""
This test class is intended for inheriting in mygame tests.
It helps ensure your tests are run with your own objects
and settings from your game folder.
"""
account_typeclass = settings.BASE_ACCOUNT_TYPECLASS
object_typeclass = settings.BASE_OBJECT_TYPECLASS
character_typeclass = settings.BASE_CHARACTER_TYPECLASS
exit_typeclass = settings.BASE_EXIT_TYPECLASS
room_typeclass = settings.BASE_ROOM_TYPECLASS
script_typeclass = settings.BASE_SCRIPT_TYPECLASS