Source code for evennia.contrib.base_systems.ingame_python.tests

"""
Module containing the test cases for the in-game Python system.
"""

from textwrap import dedent

from django.conf import settings
from mock import Mock

from evennia import ScriptDB
from evennia.commands.default.tests import BaseEvenniaCommandTest
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 BaseEvenniaTest

from .callbackhandler import CallbackHandler
from .commands import CmdCallback

# Force settings
settings.EVENTS_CALENDAR = "standard"

# Constants
OLD_EVENTS = {}


[docs]class TestEventHandler(BaseEvenniaTest): """ Test cases of the event handler to add, edit or delete events. """
[docs] def setUp(self): """Create the event handler.""" super().setUp() self.handler = create_script( "evennia.contrib.base_systems.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.base_systems.ingame_python.typeclasses.EventCharacter" ) self.char2.swap_typeclass( "evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter" ) self.room1.swap_typeclass( "evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom" ) self.room2.swap_typeclass( "evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom" ) self.exit.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventExit")
[docs] def tearDown(self): """Stop the event handler.""" OLD_EVENTS.clear() OLD_EVENTS.update(self.handler.ndb.events) self.handler.delete() CallbackHandler.script = None super().tearDown()
[docs] 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)
[docs] 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)
[docs] 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)
[docs] 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)
[docs] 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)
[docs] 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)
[docs] 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)
[docs] 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(), {})
[docs]class TestCmdCallback(BaseEvenniaCommandTest): """Test the @callback command."""
[docs] def setUp(self): """Create the callback handler.""" super().setUp() self.handler = create_script( "evennia.contrib.base_systems.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.base_systems.ingame_python.typeclasses.EventCharacter" ) self.char2.swap_typeclass( "evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter" ) self.room1.swap_typeclass( "evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom" ) self.room2.swap_typeclass( "evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom" ) self.exit.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventExit")
[docs] def tearDown(self): """Stop the callback handler.""" OLD_EVENTS.clear() OLD_EVENTS.update(self.handler.ndb.events) self.handler.delete() for script in ScriptDB.objects.filter( db_typeclass_path="evennia.contrib.base_systems.ingame_python.scripts.TimeEventScript" ): script.delete() CallbackHandler.script = None super().tearDown()
[docs] 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")
[docs] 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)
[docs] 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)
[docs] 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")
[docs] 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)
[docs]class TestDefaultCallbacks(BaseEvenniaCommandTest): """Test the default callbacks."""
[docs] def setUp(self): """Create the callback handler.""" super().setUp() self.handler = create_script( "evennia.contrib.base_systems.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.base_systems.ingame_python.typeclasses.EventCharacter" ) self.char2.swap_typeclass( "evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter" ) self.room1.swap_typeclass( "evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom" ) self.room2.swap_typeclass( "evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom" ) self.exit.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventExit")
[docs] def tearDown(self): """Stop the callback handler.""" OLD_EVENTS.clear() OLD_EVENTS.update(self.handler.ndb.events) self.handler.delete() CallbackHandler.script = None super().tearDown()
[docs] 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)) 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)) 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