taskhandler.Task created

Created an object to represent a task.
This allows for the return of TASK_HANDLER.add or utils.delay to be an object that has callable methods. It has been created to mock the most common methods and attributes of a twisted deferred object.

Changed test_utils.test_delay for new usage.
Returned previously changed modules slow_exit, tutorial_world.objects and portal.telnet to their previous states. As the return of utils.delay can be used as if it were a deferred.

All evennia unit tests pass
This commit is contained in:
davewiththenicehat 2021-04-18 18:25:39 -04:00
parent 16f6edb18d
commit f57fb645c8
6 changed files with 206 additions and 42 deletions

View file

@ -36,7 +36,6 @@ TickerHandler might be better.
"""
from evennia import DefaultExit, utils, Command
from evennia.scripts.taskhandler import TASK_HANDLER as _TASK_HANDLER
MOVE_DELAY = {"stroll": 6, "walk": 4, "run": 2, "sprint": 1}
@ -71,11 +70,11 @@ class SlowExit(DefaultExit):
traversing_object.msg("You start moving %s at a %s." % (self.key, move_speed))
# create a delayed movement
task_id = utils.delay(move_delay, move_callback)
t = utils.delay(move_delay, move_callback)
# we store the deferred on the character, this will allow us
# to abort the movement. We must use an ndb here since
# deferreds cannot be pickled.
traversing_object.ndb.currently_moving = _TASK_HANDLER.get_deferred(task_id)
traversing_object.ndb.currently_moving = t
#

View file

@ -1152,7 +1152,13 @@ from evennia.contrib import slow_exit
slow_exit.MOVE_DELAY = {"stroll": 0, "walk": 0, "run": 0, "sprint": 0}
def _cancellable_mockdelay(time, callback, *args, **kwargs):
callback(*args, **kwargs)
return Mock()
class TestSlowExit(CommandTest):
@patch("evennia.utils.delay", _cancellable_mockdelay)
def test_exit(self):
exi = create_object(
slow_exit.SlowExit, key="slowexit", location=self.room1, destination=self.room2

View file

@ -24,7 +24,6 @@ import random
from evennia import DefaultObject, DefaultExit, Command, CmdSet
from evennia.utils import search, delay, dedent
from evennia.prototypes.spawner import spawn
from evennia.scripts.taskhandler import TASK_HANDLER as _TASK_HANDLER
# -------------------------------------------------------------
#
@ -390,8 +389,7 @@ class LightSource(TutorialObject):
# start the burn timer. When it runs out, self._burnout
# will be called. We store the deferred so it can be
# killed in unittesting.
task_id = delay(60 * 3, self._burnout)
self.deferred = _TASK_HANDLER.get_deferred(task_id)
self.deferred = delay(60 * 3, self._burnout)
return True
@ -689,8 +687,7 @@ class CrumblingWall(TutorialObject, DefaultExit):
self.db.exit_open = True
# start a 45 second timer before closing again. We store the deferred so it can be
# killed in unittesting.
task_id = delay(45, self.reset)
self.deferred = _TASK_HANDLER.get_deferred(task_id)
self.deferred = delay(45, self.reset)
return True
def _translate_position(self, root, ipos):

View file

@ -209,7 +209,7 @@ class TaskHandler(object):
self.tasks[task_id] = task
else: # the task already completed
return False
return task_id
return Task(task_id)
def exists(self, task_id):
"""
@ -388,3 +388,150 @@ class TaskHandler(object):
# Create the soft singleton
TASK_HANDLER = TaskHandler()
class Task:
"""
A light
"""
def __init__(self, task_id):
self.task_id = task_id
def get_deferred(self):
"""
Return the instance of the deferred the task id is using.
Returns:
An instance of a deferral or False if there is no task with the id.
None is returned if there is no deferral affiliated with this id.
"""
return TASK_HANDLER.get_deferred(self.task_id)
def pause(self):
"""
Pause the callback of a task.
To resume use Task.unpause
"""
d = TASK_HANDLER.get_deferred(self.task_id)
if d:
d.pause()
def unpause(self):
"""
Process all callbacks made since pause() was called.
"""
d = TASK_HANDLER.get_deferred(self.task_id)
if d:
d.unpause()
@property
def paused(self):
"""
A task attribute to check if the deferral of a task has been paused.
This exists to mock usage of a twisted deferred object.
This will return None if the deferred object for the task does not
exist or if the task no longer exists.
"""
d = TASK_HANDLER.get_deferred(self.task_id)
if d:
return d.paused
else:
return None
def do_task(self):
"""
Execute the task (call its callback).
If calling before timedelay cancel the deferral affliated to this task.
Remove the task from the dictionary of current tasks on a successful
callback.
Returns:
False (bool): if the:
task no longer exists,
has no affliated instance of deferral
The return of the callback passed on task creation.
This makes it possible for the callback to also return False
None: if there was a raised exception
Note:
On a successful call the task will be removed from the dictionary
of current tasks.
"""
return TASK_HANDLER.do_task(self.task_id)
def remove(self):
"""
Remove a task without executing it.
Deletes the instance of the task's deferral.
Returns:
True (bool): if the removal completed successfully or if the a
task with the id does not exist.
None: if there was a raised exception
"""
return TASK_HANDLER.remove(self.task_id)
def cancel(self):
"""
Stop a task from automatically executing.
This will not remove the task.
Returns:
True (bool): if the removal completed successfully.
False (bool): if the task:
does not exist,
has already run,
does not have a deferral instance created for the task.
None, if there was a raised exception
"""
return TASK_HANDLER.cancel(self.task_id)
def active(self):
"""
Check if a task is active (has not been called yet).
Returns:
True (bool): If a task is active (has not been called yet).
False (bool): if the task
is not active (has already been called),
does not exist
"""
return TASK_HANDLER.active(self.task_id)
@property
def called(self):
"""
A task attribute to check if the deferral of a task has been called.
This exists to mock usage of a twisted deferred object.
It will not set to false if Task.call has been called.
"""
return not TASK_HANDLER.active(self.task_id)
def exists(self):
"""
Test if a task exists.
Returns:
True (bool): if the task exists.
False (bool): if the task does not exist.
Note:
Most task handler methods check for existence for you.
"""
return TASK_HANDLER.exists(self.task_id)
def get_id(self):
"""
Returns the global id for this task. For use with
`evennia.scripts.taskhandler.TASK_HANDLER`.
Returns:
task_id (int): global task id for this task.
"""
return self.task_id

View file

@ -31,7 +31,6 @@ from evennia.server.portal.mccp import Mccp, mccp_compress, MCCP
from evennia.server.portal.mxp import Mxp, mxp_parse
from evennia.utils import ansi
from evennia.utils.utils import to_bytes
from evennia.scripts.taskhandler import TASK_HANDLER as _TASK_HANDLER
_RE_N = re.compile(r"\|n$")
_RE_LEND = re.compile(br"\n$|\r$|\r\n$|\r\x00$|", re.MULTILINE)
@ -128,8 +127,8 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
from evennia.utils.utils import delay
task_id = delay(2, callback=self.handshake_done, timeout=True)
self._handshake_delay = _TASK_HANDLER.get_deferred(task_id)
# timeout the handshakes in case the client doesn't reply at all
self._handshake_delay = delay(2, callback=self.handshake_done, timeout=True)
# TCP/IP keepalive watches for dead links
self.transport.setTcpKeepAlive(1)

View file

@ -331,71 +331,87 @@ class TestDelay(EvenniaTest):
_TASK_HANDLER.clock = task.Clock()
self.char1.ndb.dummy_var = False
# test a persistent deferral, that completes after delay time
task_id = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=True)
self.assertTrue(_TASK_HANDLER.active(task_id))
t = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=True)
self.assertTrue(_TASK_HANDLER.active(t.get_id())) # test Task.get_id
self.assertTrue(t.active())
_TASK_HANDLER.clock.advance(timedelay) # make time pass
self.assertTrue(t.called) # test Task.called property
self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran')
self.char1.ndb.dummy_var = False
# test a persistent deferral, that completes on a manual call
task_id = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=True)
self.assertTrue(_TASK_HANDLER.active(task_id))
result = _TASK_HANDLER.do_task(task_id)
t = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=True)
self.assertTrue(t.active())
result = t.do_task()
self.assertTrue(result)
self.assertFalse(_TASK_HANDLER.exists(task_id))
self.assertFalse(t.exists())
_TASK_HANDLER.clock.advance(timedelay) # make time pass, important keep
self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran')
self.char1.ndb.dummy_var = False
# test a non persisten deferral, that completes after delay time.
utils.delay(timedelay, dummy_func, self.char1.dbref)
self.assertTrue(_TASK_HANDLER.active(task_id))
t = utils.delay(timedelay, dummy_func, self.char1.dbref)
self.assertTrue(t.active())
_TASK_HANDLER.clock.advance(timedelay) # make time pass
self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran')
self.char1.ndb.dummy_var = False
# test a non-persistent deferral, that completes on a manual call
task_id = utils.delay(timedelay, dummy_func, self.char1.dbref)
self.assertTrue(_TASK_HANDLER.active(task_id))
result = _TASK_HANDLER.do_task(task_id)
t = utils.delay(timedelay, dummy_func, self.char1.dbref)
self.assertTrue(t.active())
result = t.do_task()
self.assertTrue(result)
self.assertFalse(_TASK_HANDLER.exists(task_id))
self.assertFalse(t.exists())
_TASK_HANDLER.clock.advance(timedelay) # make time pass, important keep
self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran')
self.char1.ndb.dummy_var = False
# test a non persisten deferral, with a short timedelay
utils.delay(.1, dummy_func, self.char1.dbref)
self.assertTrue(_TASK_HANDLER.active(task_id))
t = utils.delay(.1, dummy_func, self.char1.dbref)
self.assertTrue(t.active())
_TASK_HANDLER.clock.advance(.1) # make time pass
self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran')
self.char1.ndb.dummy_var = False
# test canceling a deferral.
# after this the task_id 1 remains used by this canceled but unused task
task_id = utils.delay(timedelay, dummy_func, self.char1.dbref)
self.assertTrue(_TASK_HANDLER.active(task_id))
success = _TASK_HANDLER.cancel(task_id)
self.assertFalse(_TASK_HANDLER.active(task_id))
t = utils.delay(timedelay, dummy_func, self.char1.dbref)
self.assertTrue(t.active())
success = t.cancel()
self.assertFalse(t.active())
self.assertTrue(success)
self.assertTrue(_TASK_HANDLER.exists(task_id))
self.assertTrue(t.exists())
_TASK_HANDLER.clock.advance(timedelay) # make time pass
self.assertEqual(self.char1.ndb.dummy_var, False)
self.char1.ndb.dummy_var = False
# test removing an active task
task_id = utils.delay(timedelay, dummy_func, self.char1.dbref)
self.assertTrue(_TASK_HANDLER.active(task_id))
success = _TASK_HANDLER.remove(task_id)
self.assertFalse(_TASK_HANDLER.active(task_id))
t = utils.delay(timedelay, dummy_func, self.char1.dbref)
self.assertTrue(t.active())
success = t.remove()
self.assertFalse(t.active())
_TASK_HANDLER.clock.advance(timedelay) # make time pass
self.assertEqual(self.char1.ndb.dummy_var, False)
self.assertFalse(_TASK_HANDLER.exists(task_id))
self.assertFalse(t.exists())
self.char1.ndb.dummy_var = False
# test removing a canceled task
task_id = utils.delay(timedelay, dummy_func, self.char1.dbref)
self.assertTrue(_TASK_HANDLER.active(task_id))
deferal_inst = _TASK_HANDLER.get_deferred(task_id)
t = utils.delay(timedelay, dummy_func, self.char1.dbref)
self.assertTrue(t.active())
deferal_inst = t.get_deferred()
deferal_inst.cancel()
self.assertFalse(_TASK_HANDLER.active(task_id))
success = _TASK_HANDLER.remove(task_id)
self.assertFalse(t.active())
success = t.remove()
_TASK_HANDLER.clock.advance(timedelay) # make time pass
self.assertEqual(self.char1.ndb.dummy_var, False)
self.assertFalse(_TASK_HANDLER.exists(task_id))
self.assertFalse(t.exists())
self.char1.ndb.dummy_var = False
# test pause, paused and unpause
t = utils.delay(timedelay, dummy_func, self.char1.dbref)
self.assertTrue(t.active())
t.pause()
self.assertTrue(t.paused)
t.unpause()
self.assertFalse(t.paused)
self.assertEqual(self.char1.ndb.dummy_var, False)
t.pause()
_TASK_HANDLER.clock.advance(timedelay) # make time pass
self.assertEqual(self.char1.ndb.dummy_var, False)
t.unpause()
self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran')
self.char1.ndb.dummy_var = False
# restart condictions
# cancel a diferall directly, without calling task handler's cancel