mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
517 lines
21 KiB
Python
517 lines
21 KiB
Python
"""
|
|
Module containing the test cases for the in-game Python system.
|
|
"""
|
|
|
|
from mock import Mock
|
|
from textwrap import dedent
|
|
|
|
from django.conf import settings
|
|
from evennia import ScriptDB
|
|
from evennia.commands.default.tests import CommandTest
|
|
from evennia.objects.objects import ExitCommand
|
|
from evennia.utils import ansi, utils
|
|
from evennia.utils.create import create_object, create_script
|
|
from evennia.utils.test_resources import EvenniaTest
|
|
from evennia.contrib.ingame_python.commands import CmdCallback
|
|
from evennia.contrib.ingame_python.callbackhandler import CallbackHandler
|
|
|
|
# Force settings
|
|
settings.EVENTS_CALENDAR = "standard"
|
|
|
|
# Constants
|
|
OLD_EVENTS = {}
|
|
|
|
|
|
class TestEventHandler(EvenniaTest):
|
|
|
|
"""
|
|
Test cases of the event handler to add, edit or delete events.
|
|
"""
|
|
|
|
def setUp(self):
|
|
"""Create the event handler."""
|
|
super(TestEventHandler, self).setUp()
|
|
self.handler = create_script(
|
|
"evennia.contrib.ingame_python.scripts.EventHandler")
|
|
|
|
# Copy old events if necessary
|
|
if OLD_EVENTS:
|
|
self.handler.ndb.events = dict(OLD_EVENTS)
|
|
|
|
# Alter typeclasses
|
|
self.char1.swap_typeclass("evennia.contrib.ingame_python.typeclasses.EventCharacter")
|
|
self.char2.swap_typeclass("evennia.contrib.ingame_python.typeclasses.EventCharacter")
|
|
self.room1.swap_typeclass("evennia.contrib.ingame_python.typeclasses.EventRoom")
|
|
self.room2.swap_typeclass("evennia.contrib.ingame_python.typeclasses.EventRoom")
|
|
self.exit.swap_typeclass("evennia.contrib.ingame_python.typeclasses.EventExit")
|
|
|
|
def tearDown(self):
|
|
"""Stop the event handler."""
|
|
OLD_EVENTS.clear()
|
|
OLD_EVENTS.update(self.handler.ndb.events)
|
|
self.handler.stop()
|
|
CallbackHandler.script = None
|
|
super(TestEventHandler, self).tearDown()
|
|
|
|
def test_start(self):
|
|
"""Simply make sure the handler runs with proper initial values."""
|
|
self.assertEqual(self.handler.db.callbacks, {})
|
|
self.assertEqual(self.handler.db.to_valid, [])
|
|
self.assertEqual(self.handler.db.locked, [])
|
|
self.assertEqual(self.handler.db.tasks, {})
|
|
self.assertIsNotNone(self.handler.ndb.events)
|
|
|
|
def test_add_validation(self):
|
|
"""Add a callback while needing validation."""
|
|
author = self.char1
|
|
self.handler.add_callback(self.room1, "dummy",
|
|
"character.db.strength = 40", author=author, valid=False)
|
|
callback = self.handler.get_callbacks(self.room1).get("dummy")
|
|
callback = callback[0]
|
|
self.assertIsNotNone(callback)
|
|
self.assertEqual(callback["author"], author)
|
|
self.assertEqual(callback["valid"], False)
|
|
|
|
# Since this callback is not valid, it should appear in 'to_valid'
|
|
self.assertIn((self.room1, "dummy", 0), self.handler.db.to_valid)
|
|
|
|
# Run this dummy callback (shouldn't do anything)
|
|
self.char1.db.strength = 10
|
|
locals = {"character": self.char1}
|
|
self.assertTrue(self.handler.call(
|
|
self.room1, "dummy", locals=locals))
|
|
self.assertEqual(self.char1.db.strength, 10)
|
|
|
|
def test_edit(self):
|
|
"""Test editing a callback."""
|
|
author = self.char1
|
|
self.handler.add_callback(self.room1, "dummy",
|
|
"character.db.strength = 60", author=author, valid=True)
|
|
|
|
# Edit it right away
|
|
self.handler.edit_callback(self.room1, "dummy", 0,
|
|
"character.db.strength = 65", author=self.char2, valid=True)
|
|
|
|
# Check that the callback was written
|
|
callback = self.handler.get_callbacks(self.room1).get("dummy")
|
|
callback = callback[0]
|
|
self.assertIsNotNone(callback)
|
|
self.assertEqual(callback["author"], author)
|
|
self.assertEqual(callback["valid"], True)
|
|
self.assertEqual(callback["updated_by"], self.char2)
|
|
|
|
# Run this dummy callback
|
|
self.char1.db.strength = 10
|
|
locals = {"character": self.char1}
|
|
self.assertTrue(self.handler.call(
|
|
self.room1, "dummy", locals=locals))
|
|
self.assertEqual(self.char1.db.strength, 65)
|
|
|
|
def test_edit_validation(self):
|
|
"""Edit a callback when validation isn't automatic."""
|
|
author = self.char1
|
|
self.handler.add_callback(self.room1, "dummy",
|
|
"character.db.strength = 70", author=author, valid=True)
|
|
|
|
# Edit it right away
|
|
self.handler.edit_callback(self.room1, "dummy", 0,
|
|
"character.db.strength = 80", author=self.char2, valid=False)
|
|
|
|
# Run this dummy callback (shouldn't do anything)
|
|
self.char1.db.strength = 10
|
|
locals = {"character": self.char1}
|
|
self.assertTrue(self.handler.call(
|
|
self.room1, "dummy", locals=locals))
|
|
self.assertEqual(self.char1.db.strength, 10)
|
|
|
|
def test_del(self):
|
|
"""Try to delete a callback."""
|
|
# Add 3 callbacks
|
|
self.handler.add_callback(self.room1, "dummy",
|
|
"character.db.strength = 5", author=self.char1, valid=True)
|
|
self.handler.add_callback(self.room1, "dummy",
|
|
"character.db.strength = 8", author=self.char2, valid=False)
|
|
self.handler.add_callback(self.room1, "dummy",
|
|
"character.db.strength = 9", author=self.char1, valid=True)
|
|
|
|
# Note that the second callback isn't valid
|
|
self.assertIn((self.room1, "dummy", 1), self.handler.db.to_valid)
|
|
|
|
# Lock the third callback
|
|
self.handler.db.locked.append((self.room1, "dummy", 2))
|
|
|
|
# Delete the first callback
|
|
self.handler.del_callback(self.room1, "dummy", 0)
|
|
|
|
# The callback #1 that was to valid should be #0 now
|
|
self.assertIn((self.room1, "dummy", 0), self.handler.db.to_valid)
|
|
self.assertNotIn((self.room1, "dummy", 1), self.handler.db.to_valid)
|
|
|
|
# The lock has been updated too
|
|
self.assertIn((self.room1, "dummy", 1), self.handler.db.locked)
|
|
self.assertNotIn((self.room1, "dummy", 2), self.handler.db.locked)
|
|
|
|
# Now delete the first (not valid) callback
|
|
self.handler.del_callback(self.room1, "dummy", 0)
|
|
self.assertEqual(self.handler.db.to_valid, [])
|
|
self.assertIn((self.room1, "dummy", 0), self.handler.db.locked)
|
|
self.assertNotIn((self.room1, "dummy", 1), self.handler.db.locked)
|
|
|
|
# Call the remaining callback
|
|
self.char1.db.strength = 10
|
|
locals = {"character": self.char1}
|
|
self.assertTrue(self.handler.call(
|
|
self.room1, "dummy", locals=locals))
|
|
self.assertEqual(self.char1.db.strength, 9)
|
|
|
|
def test_accept(self):
|
|
"""Accept an callback."""
|
|
# Add 2 callbacks
|
|
self.handler.add_callback(self.room1, "dummy",
|
|
"character.db.strength = 5", author=self.char1, valid=True)
|
|
self.handler.add_callback(self.room1, "dummy",
|
|
"character.db.strength = 8", author=self.char2, valid=False)
|
|
|
|
# Note that the second callback isn't valid
|
|
self.assertIn((self.room1, "dummy", 1), self.handler.db.to_valid)
|
|
|
|
# Accept the second callback
|
|
self.handler.accept_callback(self.room1, "dummy", 1)
|
|
callback = self.handler.get_callbacks(self.room1).get("dummy")
|
|
callback = callback[1]
|
|
self.assertIsNotNone(callback)
|
|
self.assertEqual(callback["valid"], True)
|
|
|
|
# Call the dummy callback
|
|
self.char1.db.strength = 10
|
|
locals = {"character": self.char1}
|
|
self.assertTrue(self.handler.call(
|
|
self.room1, "dummy", locals=locals))
|
|
self.assertEqual(self.char1.db.strength, 8)
|
|
|
|
def test_call(self):
|
|
"""Test to call amore complex callback."""
|
|
self.char1.key = "one"
|
|
self.char2.key = "two"
|
|
|
|
# Add an callback
|
|
code = dedent("""
|
|
if character.key == "one":
|
|
character.db.health = 50
|
|
else:
|
|
character.db.health = 0
|
|
""".strip("\n"))
|
|
self.handler.add_callback(self.room1, "dummy", code,
|
|
author=self.char1, valid=True)
|
|
|
|
# Call the dummy callback
|
|
self.assertTrue(self.handler.call(
|
|
self.room1, "dummy", locals={"character": self.char1}))
|
|
self.assertEqual(self.char1.db.health, 50)
|
|
self.assertTrue(self.handler.call(
|
|
self.room1, "dummy", locals={"character": self.char2}))
|
|
self.assertEqual(self.char2.db.health, 0)
|
|
|
|
def test_handler(self):
|
|
"""Test the object handler."""
|
|
self.assertIsNotNone(self.char1.callbacks)
|
|
|
|
# Add an callback
|
|
callback = self.room1.callbacks.add("dummy", "pass", author=self.char1,
|
|
valid=True)
|
|
self.assertEqual(callback.obj, self.room1)
|
|
self.assertEqual(callback.name, "dummy")
|
|
self.assertEqual(callback.code, "pass")
|
|
self.assertEqual(callback.author, self.char1)
|
|
self.assertEqual(callback.valid, True)
|
|
self.assertIn([callback], list(self.room1.callbacks.all().values()))
|
|
|
|
# Edit this very callback
|
|
new = self.room1.callbacks.edit("dummy", 0, "character.db.say = True",
|
|
author=self.char1, valid=True)
|
|
self.assertIn([new], list(self.room1.callbacks.all().values()))
|
|
self.assertNotIn([callback], list(self.room1.callbacks.all().values()))
|
|
|
|
# Try to call this callback
|
|
self.assertTrue(self.room1.callbacks.call("dummy",
|
|
locals={"character": self.char2}))
|
|
self.assertTrue(self.char2.db.say)
|
|
|
|
# Delete the callback
|
|
self.room1.callbacks.remove("dummy", 0)
|
|
self.assertEqual(self.room1.callbacks.all(), {})
|
|
|
|
|
|
class TestCmdCallback(CommandTest):
|
|
|
|
"""Test the @callback command."""
|
|
|
|
def setUp(self):
|
|
"""Create the callback handler."""
|
|
super(TestCmdCallback, self).setUp()
|
|
self.handler = create_script(
|
|
"evennia.contrib.ingame_python.scripts.EventHandler")
|
|
|
|
# Copy old events if necessary
|
|
if OLD_EVENTS:
|
|
self.handler.ndb.events = dict(OLD_EVENTS)
|
|
|
|
# Alter typeclasses
|
|
self.char1.swap_typeclass("evennia.contrib.ingame_python.typeclasses.EventCharacter")
|
|
self.char2.swap_typeclass("evennia.contrib.ingame_python.typeclasses.EventCharacter")
|
|
self.room1.swap_typeclass("evennia.contrib.ingame_python.typeclasses.EventRoom")
|
|
self.room2.swap_typeclass("evennia.contrib.ingame_python.typeclasses.EventRoom")
|
|
self.exit.swap_typeclass("evennia.contrib.ingame_python.typeclasses.EventExit")
|
|
|
|
def tearDown(self):
|
|
"""Stop the callback handler."""
|
|
OLD_EVENTS.clear()
|
|
OLD_EVENTS.update(self.handler.ndb.events)
|
|
self.handler.stop()
|
|
for script in ScriptDB.objects.filter(
|
|
db_typeclass_path="evennia.contrib.ingame_python.scripts.TimeEventScript"):
|
|
script.stop()
|
|
|
|
CallbackHandler.script = None
|
|
super(TestCmdCallback, self).tearDown()
|
|
|
|
def test_list(self):
|
|
"""Test listing callbacks with different rights."""
|
|
table = self.call(CmdCallback(), "out")
|
|
lines = table.splitlines()[3:-1]
|
|
self.assertNotEqual(lines, [])
|
|
|
|
# Check that the second column only contains 0 (0) (no callback yet)
|
|
for line in lines:
|
|
cols = line.split("|")
|
|
self.assertIn(cols[2].strip(), ("0 (0)", ""))
|
|
|
|
# Add some callback
|
|
self.handler.add_callback(self.exit, "traverse", "pass",
|
|
author=self.char1, valid=True)
|
|
|
|
# Try to obtain more details on a specific callback on exit
|
|
table = self.call(CmdCallback(), "out = traverse")
|
|
lines = table.splitlines()[3:-1]
|
|
self.assertEqual(len(lines), 1)
|
|
line = lines[0]
|
|
cols = line.split("|")
|
|
self.assertIn(cols[1].strip(), ("1", ""))
|
|
self.assertIn(cols[2].strip(), (str(self.char1), ""))
|
|
self.assertIn(cols[-1].strip(), ("Yes", "No", ""))
|
|
|
|
# Run the same command with char2
|
|
# char2 shouldn't see the last column (Valid)
|
|
table = self.call(CmdCallback(), "out = traverse", caller=self.char2)
|
|
lines = table.splitlines()[3:-1]
|
|
self.assertEqual(len(lines), 1)
|
|
line = lines[0]
|
|
cols = line.split("|")
|
|
self.assertEqual(cols[1].strip(), "1")
|
|
self.assertNotIn(cols[-1].strip(), ("Yes", "No"))
|
|
|
|
# In any case, display the callback
|
|
# The last line should be "pass" (the callback code)
|
|
details = self.call(CmdCallback(), "out = traverse 1")
|
|
self.assertEqual(details.splitlines()[-1], "pass")
|
|
|
|
def test_add(self):
|
|
"""Test to add an callback."""
|
|
self.call(CmdCallback(), "/add out = traverse")
|
|
editor = self.char1.ndb._eveditor
|
|
self.assertIsNotNone(editor)
|
|
|
|
# Edit the callback
|
|
editor.update_buffer(dedent("""
|
|
if character.key == "one":
|
|
character.msg("You can pass.")
|
|
else:
|
|
character.msg("You can't pass.")
|
|
deny()
|
|
""".strip("\n")))
|
|
editor.save_buffer()
|
|
editor.quit()
|
|
callback = self.exit.callbacks.get("traverse")[0]
|
|
self.assertEqual(callback.author, self.char1)
|
|
self.assertEqual(callback.valid, True)
|
|
self.assertTrue(len(callback.code) > 0)
|
|
|
|
# We're going to try the same thing but with char2
|
|
# char2 being a player for our test, the callback won't be validated.
|
|
self.call(CmdCallback(), "/add out = traverse", caller=self.char2)
|
|
editor = self.char2.ndb._eveditor
|
|
self.assertIsNotNone(editor)
|
|
|
|
# Edit the callback
|
|
editor.update_buffer(dedent("""
|
|
character.msg("No way.")
|
|
""".strip("\n")))
|
|
editor.save_buffer()
|
|
editor.quit()
|
|
callback = self.exit.callbacks.get("traverse")[1]
|
|
self.assertEqual(callback.author, self.char2)
|
|
self.assertEqual(callback.valid, False)
|
|
self.assertTrue(len(callback.code) > 0)
|
|
|
|
def test_del(self):
|
|
"""Add and remove an callback."""
|
|
self.handler.add_callback(self.exit, "traverse", "pass",
|
|
author=self.char1, valid=True)
|
|
|
|
# Try to delete the callback
|
|
# char2 shouldn't be allowed to do so (that's not HIS callback)
|
|
self.call(CmdCallback(), "/del out = traverse 1", caller=self.char2)
|
|
self.assertTrue(len(self.handler.get_callbacks(self.exit).get(
|
|
"traverse", [])) == 1)
|
|
|
|
# Now, char1 should be allowed to delete it
|
|
self.call(CmdCallback(), "/del out = traverse 1")
|
|
self.assertTrue(len(self.handler.get_callbacks(self.exit).get(
|
|
"traverse", [])) == 0)
|
|
|
|
def test_lock(self):
|
|
"""Test the lock of multiple editing."""
|
|
self.call(CmdCallback(), "/add here = time 8:00", caller=self.char2)
|
|
self.assertIsNotNone(self.char2.ndb._eveditor)
|
|
|
|
# Now ask char1 to edit
|
|
line = self.call(CmdCallback(), "/edit here = time 1")
|
|
self.assertIsNone(self.char1.ndb._eveditor)
|
|
|
|
# Try to delete this callback while char2 is editing it
|
|
line = self.call(CmdCallback(), "/del here = time 1")
|
|
|
|
def test_accept(self):
|
|
"""Accept an callback."""
|
|
self.call(CmdCallback(), "/add here = time 8:00", caller=self.char2)
|
|
editor = self.char2.ndb._eveditor
|
|
self.assertIsNotNone(editor)
|
|
|
|
# Edit the callback
|
|
editor.update_buffer(dedent("""
|
|
room.msg_contents("It's 8 PM, everybody up!")
|
|
""".strip("\n")))
|
|
editor.save_buffer()
|
|
editor.quit()
|
|
callback = self.room1.callbacks.get("time")[0]
|
|
self.assertEqual(callback.valid, False)
|
|
|
|
# chars shouldn't be allowed to the callback
|
|
self.call(CmdCallback(), "/accept here = time 1", caller=self.char2)
|
|
callback = self.room1.callbacks.get("time")[0]
|
|
self.assertEqual(callback.valid, False)
|
|
|
|
# char1 will accept the callback
|
|
self.call(CmdCallback(), "/accept here = time 1")
|
|
callback = self.room1.callbacks.get("time")[0]
|
|
self.assertEqual(callback.valid, True)
|
|
|
|
|
|
class TestDefaultCallbacks(CommandTest):
|
|
|
|
"""Test the default callbacks."""
|
|
|
|
def setUp(self):
|
|
"""Create the callback handler."""
|
|
super(TestDefaultCallbacks, self).setUp()
|
|
self.handler = create_script(
|
|
"evennia.contrib.ingame_python.scripts.EventHandler")
|
|
|
|
# Copy old events if necessary
|
|
if OLD_EVENTS:
|
|
self.handler.ndb.events = dict(OLD_EVENTS)
|
|
|
|
# Alter typeclasses
|
|
self.char1.swap_typeclass("evennia.contrib.ingame_python.typeclasses.EventCharacter")
|
|
self.char2.swap_typeclass("evennia.contrib.ingame_python.typeclasses.EventCharacter")
|
|
self.room1.swap_typeclass("evennia.contrib.ingame_python.typeclasses.EventRoom")
|
|
self.room2.swap_typeclass("evennia.contrib.ingame_python.typeclasses.EventRoom")
|
|
self.exit.swap_typeclass("evennia.contrib.ingame_python.typeclasses.EventExit")
|
|
|
|
def tearDown(self):
|
|
"""Stop the callback handler."""
|
|
OLD_EVENTS.clear()
|
|
OLD_EVENTS.update(self.handler.ndb.events)
|
|
self.handler.stop()
|
|
CallbackHandler.script = None
|
|
super(TestDefaultCallbacks, self).tearDown()
|
|
|
|
def test_exit(self):
|
|
"""Test the callbacks of an exit."""
|
|
self.char1.key = "char1"
|
|
code = dedent("""
|
|
if character.key == "char1":
|
|
character.msg("You can leave.")
|
|
else:
|
|
character.msg("You cannot leave.")
|
|
deny()
|
|
""".strip("\n"))
|
|
# Enforce self.exit.destination since swapping typeclass lose it
|
|
self.exit.destination = self.room2
|
|
|
|
# Try the can_traverse callback
|
|
self.handler.add_callback(self.exit, "can_traverse", code,
|
|
author=self.char1, valid=True)
|
|
|
|
# Have char1 move through the exit
|
|
self.call(ExitCommand(), "", "You can leave.", obj=self.exit)
|
|
self.assertIs(self.char1.location, self.room2)
|
|
|
|
# Have char2 move through this exit
|
|
self.call(ExitCommand(), "", "You cannot leave.", obj=self.exit,
|
|
caller=self.char2)
|
|
self.assertIs(self.char2.location, self.room1)
|
|
|
|
# Try the traverse callback
|
|
self.handler.del_callback(self.exit, "can_traverse", 0)
|
|
self.handler.add_callback(self.exit, "traverse", "character.msg('Fine!')",
|
|
author=self.char1, valid=True)
|
|
|
|
# Have char2 move through the exit
|
|
self.call(ExitCommand(), "", obj=self.exit, caller=self.char2)
|
|
self.assertIs(self.char2.location, self.room2)
|
|
self.handler.del_callback(self.exit, "traverse", 0)
|
|
|
|
# Move char1 and char2 back
|
|
self.char1.location = self.room1
|
|
self.char2.location = self.room1
|
|
|
|
# Test msg_arrive and msg_leave
|
|
code = 'message = "{character} goes out."'
|
|
self.handler.add_callback(self.exit, "msg_leave", code,
|
|
author=self.char1, valid=True)
|
|
|
|
# Have char1 move through the exit
|
|
old_msg = self.char2.msg
|
|
try:
|
|
self.char2.msg = Mock()
|
|
self.call(ExitCommand(), "", obj=self.exit)
|
|
stored_msg = [args[0] if args and args[0] else kwargs.get("text", utils.to_str(kwargs, force_string=True))
|
|
for name, args, kwargs in self.char2.msg.mock_calls]
|
|
# Get the first element of a tuple if msg received a tuple instead of a string
|
|
stored_msg = [smsg[0] if isinstance(smsg, tuple) else smsg for smsg in stored_msg]
|
|
returned_msg = ansi.parse_ansi("\n".join(stored_msg), strip_ansi=True)
|
|
self.assertEqual(returned_msg, "char1 goes out.")
|
|
finally:
|
|
self.char2.msg = old_msg
|
|
|
|
# Create a return exit
|
|
back = create_object("evennia.objects.objects.DefaultExit",
|
|
key="in", location=self.room2, destination=self.room1)
|
|
code = 'message = "{character} goes in."'
|
|
self.handler.add_callback(self.exit, "msg_arrive", code,
|
|
author=self.char1, valid=True)
|
|
|
|
# Have char1 move through the exit
|
|
old_msg = self.char2.msg
|
|
try:
|
|
self.char2.msg = Mock()
|
|
self.call(ExitCommand(), "", obj=back)
|
|
stored_msg = [args[0] if args and args[0] else kwargs.get("text", utils.to_str(kwargs, force_string=True))
|
|
for name, args, kwargs in self.char2.msg.mock_calls]
|
|
# Get the first element of a tuple if msg received a tuple instead of a string
|
|
stored_msg = [smsg[0] if isinstance(smsg, tuple) else smsg for smsg in stored_msg]
|
|
returned_msg = ansi.parse_ansi("\n".join(stored_msg), strip_ansi=True)
|
|
self.assertEqual(returned_msg, "char1 goes in.")
|
|
finally:
|
|
self.char2.msg = old_msg
|