Change how cmdset options are merged by priority - this is now a straight priority order, where the option from the higher prio goes. Also add unit tests for cmdset mergers.

This commit is contained in:
Griatch 2016-10-08 19:39:52 +02:00
parent 300758b2dd
commit 40e1c67f88
3 changed files with 212 additions and 38 deletions

View file

@ -329,7 +329,7 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype):
prio = cmdset.priority prio = cmdset.priority
if prio in tempmergers: if prio in tempmergers:
# merge same-prio cmdset together separately # merge same-prio cmdset together separately
tempmergers[prio] = yield cmdset + tempmergers[prio] tempmergers[prio] = yield tempmergers[prio] + cmdset
else: else:
tempmergers[prio] = cmdset tempmergers[prio] = cmdset
@ -339,7 +339,7 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype):
# Merge all command sets into one, beginning with the lowest-prio one # Merge all command sets into one, beginning with the lowest-prio one
cmdset = cmdsets[0] cmdset = cmdsets[0]
for merging_cmdset in cmdsets[1:]: for merging_cmdset in cmdsets[1:]:
cmdset = yield merging_cmdset + cmdset cmdset = yield cmdset + merging_cmdset
# store the full sets for diagnosis # store the full sets for diagnosis
cmdset.merged_from = cmdsets cmdset.merged_from = cmdsets
# cache # cache

View file

@ -351,52 +351,61 @@ class CmdSet(with_metaclass(_CmdSetMeta, object)):
self._contains_cache[othercmd] = ret self._contains_cache[othercmd] = ret
return ret return ret
def __add__(self, cmdset_b): def __add__(self, cmdset_a):
""" """
Merge this cmdset (A) with another cmdset (B) using the + operator, Merge this cmdset (B) with another cmdset (A) using the + operator,
C = A + B C = B + A
Here, we (by convention) say that 'A is merged onto B to form Here, we (by convention) say that 'A is merged onto B to form
C'. The actual merge operation used in the 'addition' depends C'. The actual merge operation used in the 'addition' depends
on which priorities A and B have. The one of the two with the on which priorities A and B have. The one of the two with the
highest priority will apply and give its properties to C. In highest priority will apply and give its properties to C. In
the case of a tie, A takes priority and replaces the the case of a tie, A takes priority and replaces the
same-named commands in B unless A has the 'duplicate' variable same-named commands in B unless A has the 'duplicate' variable
set (which means both sets' commands are kept). set (which means both sets' commands are kept).
""" """
# It's okay to merge with None # It's okay to merge with None
if not cmdset_b: if not cmdset_a:
return self return self
sys_commands_a = self.get_system_cmds() sys_commands_a = cmdset_a.get_system_cmds()
sys_commands_b = cmdset_b.get_system_cmds() sys_commands_b = self.get_system_cmds()
if self.priority >= cmdset_b.priority: if self.priority <= cmdset_a.priority:
# A higher or equal priority than B # A higher or equal priority to B
# preserve system __commands # preserve system __commands
sys_commands = sys_commands_a + [cmd for cmd in sys_commands_b sys_commands = sys_commands_a + [cmd for cmd in sys_commands_b
if cmd not in sys_commands_a] if cmd not in sys_commands_a]
mergetype = self.key_mergetypes.get(cmdset_b.key, self.mergetype) mergetype = cmdset_a.key_mergetypes.get(self.key, cmdset_a.mergetype)
if mergetype == "Intersect": if mergetype == "Intersect":
cmdset_c = self._intersect(self, cmdset_b) cmdset_c = self._intersect(cmdset_a, self)
elif mergetype == "Replace": elif mergetype == "Replace":
cmdset_c = self._replace(self, cmdset_b) cmdset_c = self._replace(cmdset_a, self)
elif mergetype == "Remove": elif mergetype == "Remove":
cmdset_c = self._remove(self, cmdset_b) cmdset_c = self._remove(cmdset_a, self)
else: # Union else: # Union
cmdset_c = self._union(self, cmdset_b) cmdset_c = self._union(cmdset_a, self)
# update or pass-through
cmdset_c.no_channels = cmdset_b.no_channels if self.no_channels is None else self.no_channels if self.priority == cmdset_a.priority:
cmdset_c.no_exits = cmdset_b.no_exits if self.no_exits is None else self.no_exits # same prio - pass through if changed
cmdset_c.no_objs = cmdset_b.no_objs if self.no_objs is None else self.no_objs cmdset_c.no_channels = self.no_channels if cmdset_a.no_channels is None else cmdset_a.no_channels
cmdset_c.duplicates = cmdset_b.duplicates if self.duplicates is None else self.duplicates cmdset_c.no_exits = self.no_exits if cmdset_a.no_exits is None else cmdset_a.no_exits
cmdset_c.no_objs = self.no_objs if cmdset_a.no_objs is None else cmdset_a.no_objs
cmdset_c.duplicates = self.duplicates if cmdset_a.duplicates is None else cmdset_a.duplicates
else:
# pass through the options of the higher prio set
cmdset_c.no_channels = cmdset_a.no_channels
cmdset_c.no_exits = cmdset_a.no_exits
cmdset_c.no_objs = cmdset_a.no_objs
cmdset_c.duplicates = cmdset_a.duplicates
if self.key.startswith("_"): if self.key.startswith("_"):
# don't rename new output if the merge set's name starts with _ # don't rename new output if the merge set's name starts with _
cmdset_c.key = cmdset_b.key cmdset_c.key = cmdset_a.key
else: else:
# B higher priority than A # B higher priority than A
@ -405,32 +414,33 @@ class CmdSet(with_metaclass(_CmdSetMeta, object)):
sys_commands = sys_commands_b + [cmd for cmd in sys_commands_a sys_commands = sys_commands_b + [cmd for cmd in sys_commands_a
if cmd not in sys_commands_b] if cmd not in sys_commands_b]
mergetype = cmdset_b.key_mergetypes.get(self.key, cmdset_b.mergetype) mergetype = self.key_mergetypes.get(cmdset_a.key, self.mergetype)
if mergetype == "Intersect": if mergetype == "Intersect":
cmdset_c = self._intersect(cmdset_b, self) cmdset_c = self._intersect(self, cmdset_a)
elif mergetype == "Replace": elif mergetype == "Replace":
cmdset_c = self._replace(cmdset_b, self) cmdset_c = self._replace(self, cmdset_a)
elif mergetype == "Remove": elif mergetype == "Remove":
cmdset_c = self._remove(cmdset_b, self) cmdset_c = self._remove(self, cmdset_a)
else: # Union else: # Union
cmdset_c = self._union(cmdset_b, self) cmdset_c = self._union(self, cmdset_a)
cmdset_c.no_channels = cmdset_b.no_channels
cmdset_c.no_exits = cmdset_b.no_exits cmdset_c.no_channels = self.no_channels
cmdset_c.no_objs = cmdset_b.no_objs cmdset_c.no_exits = self.no_exits
cmdset_c.no_objs = self.no_objs
cmdset_c.duplicates = self.duplicates
# update or pass-through # update or pass-through
cmdset_c.no_channels = self.no_channels if self.no_channels is None else cmdset_b.no_channels if self.key.startswith("_"):
cmdset_c.no_exits = self.no_exits if self.no_exits is None else cmdset_b.no_exits
cmdset_c.no_objs = self.no_objs if self.no_objs is None else cmdset_b.no_objs
cmdset_c.duplicates = self.duplicates if self.duplicates is None else cmdset_b.duplicates
if cmdset_b.key.startswith("_"):
# don't rename new output if the merge set's name starts with _ # don't rename new output if the merge set's name starts with _
cmdset_c.key = self.key cmdset_c.key = cmdset_a.self.key
# we store actual_mergetype since key_mergetypes # we store actual_mergetype since key_mergetypes
# might be different from the main mergetype. # might be different from the main mergetype.
# This is used for diagnosis. # This is used for diagnosis.
cmdset_c.actual_mergetype = mergetype cmdset_c.actual_mergetype = mergetype
#print "__add__ for %s (prio %i) called with %s (prio %i)." % (self.key, self.priority, cmdset_a.key, cmdset_a.priority)
# return the system commands to the cmdset # return the system commands to the cmdset
cmdset_c.add(sys_commands) cmdset_c.add(sys_commands)
return cmdset_c return cmdset_c

164
evennia/commands/tests.py Normal file
View file

@ -0,0 +1,164 @@
"""
Unit testing for the Command system itself.
"""
from evennia.utils.test_resources import EvenniaTest, SESSIONS
from evennia.commands.cmdset import CmdSet
from evennia.commands.command import Command
# Testing-command sets
class _CmdA(Command):
key = "A"
def __init__(self, cmdset, *args, **kwargs):
super(_CmdA, self).__init__(*args, **kwargs)
self.from_cmdset = cmdset
class _CmdB(Command):
key = "B"
def __init__(self, cmdset, *args, **kwargs):
super(_CmdB, self).__init__(*args, **kwargs)
self.from_cmdset = cmdset
class _CmdC(Command):
key = "C"
def __init__(self, cmdset, *args, **kwargs):
super(_CmdC, self).__init__(*args, **kwargs)
self.from_cmdset = cmdset
class _CmdD(Command):
key = "D"
def __init__(self, cmdset, *args, **kwargs):
super(_CmdD, self).__init__(*args, **kwargs)
self.from_cmdset = cmdset
class _CmdSetA(CmdSet):
key = "A"
def at_cmdset_creation(self):
self.add(_CmdA("A"))
self.add(_CmdB("A"))
self.add(_CmdC("A"))
self.add(_CmdD("A"))
class _CmdSetB(CmdSet):
key = "B"
def at_cmdset_creation(self):
self.add(_CmdA("B"))
self.add(_CmdB("B"))
self.add(_CmdC("B"))
class _CmdSetC(CmdSet):
key = "C"
def at_cmdset_creation(self):
self.add(_CmdA("C"))
self.add(_CmdB("C"))
class _CmdSetD(CmdSet):
key = "D"
def at_cmdset_creation(self):
self.add(_CmdA("D"))
self.add(_CmdB("D"))
self.add(_CmdC("D"))
self.add(_CmdD("D"))
# testing Command Sets
class TestCmdSetMergers(EvenniaTest):
"Test merging of cmdsets"
def setUp(self):
super(TestCmdSetMergers, self).setUp()
self.cmdset_a = _CmdSetA()
self.cmdset_b = _CmdSetB()
self.cmdset_c = _CmdSetC()
self.cmdset_d = _CmdSetD()
def test_order(self):
"Merge in reverse- and forward orders, same priorities"
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
cmdset_f = d + c + b + a # merge in reverse order of priority
self.assertEqual(cmdset_f.priority, 0)
self.assertEqual(cmdset_f.mergetype, "Union")
self.assertEqual(len(cmdset_f.commands), 4)
self.assertTrue(all(True for cmd in cmdset_f.commands if cmd.from_cmdset == "A"))
cmdset_f = a + b + c + d # merge in order of priority
self.assertEqual(cmdset_f.priority, 0)
self.assertEqual(cmdset_f.mergetype, "Union")
self.assertEqual(len(cmdset_f.commands), 4) # duplicates setting from A transfers
self.assertTrue(all(True for cmd in cmdset_f.commands if cmd.from_cmdset == "D"))
def test_priority_order(self):
"Merge in reverse- and forward order with well-defined prioritities"
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
a.priority = 2
b.priority = 1
c.priority = 0
d.priority = -1
cmdset_f = d + c + b + a # merge in reverse order of priority
self.assertEqual(cmdset_f.priority, 2)
self.assertEqual(cmdset_f.mergetype, "Union")
self.assertEqual(len(cmdset_f.commands), 4)
self.assertTrue(all(True for cmd in cmdset_f.commands if cmd.from_cmdset == "A"))
cmdset_f = a + b + c + d # merge in order of priority
self.assertEqual(cmdset_f.priority, 2)
self.assertEqual(cmdset_f.mergetype, "Union")
self.assertEqual(len(cmdset_f.commands), 4)
self.assertTrue(all(True for cmd in cmdset_f.commands if cmd.from_cmdset == "A"))
def test_option_transfer(self):
"Test transfer of cmdset options"
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
a.no_exits = True
a.no_objs = True
a.no_channels = True
a.duplicates = True
cmdset_f = d + c + b + a # reverse, same-prio
self.assertTrue(cmdset_f.no_exits)
self.assertTrue(cmdset_f.no_objs)
self.assertTrue(cmdset_f.no_channels)
self.assertTrue(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 8)
cmdset_f = a + b + c + d # forward, same-prio
self.assertTrue(cmdset_f.no_exits)
self.assertTrue(cmdset_f.no_objs)
self.assertTrue(cmdset_f.no_channels)
self.assertTrue(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 4)
a.priority = 2
b.priority = 1
c.priority = 0
d.priority = -1
cmdset_f = d + c + b + a # reverse, A top priority
self.assertTrue(cmdset_f.no_exits)
self.assertTrue(cmdset_f.no_objs)
self.assertTrue(cmdset_f.no_channels)
self.assertTrue(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 4)
cmdset_f = a + b + c + d # forward, A top priority
self.assertTrue(cmdset_f.no_exits)
self.assertTrue(cmdset_f.no_objs)
self.assertTrue(cmdset_f.no_channels)
self.assertTrue(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 4)
a.priority = -1
b.priority = 0
c.priority = 1
d.priority = 2
cmdset_f = d + c + b + a # reverse, A low prio
self.assertFalse(cmdset_f.no_exits)
self.assertFalse(cmdset_f.no_objs)
self.assertFalse(cmdset_f.no_channels)
self.assertFalse(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 4)
cmdset_f = a + b + c + d # forward, A low prio
self.assertFalse(cmdset_f.no_exits)
self.assertFalse(cmdset_f.no_objs)
self.assertFalse(cmdset_f.no_channels)
self.assertFalse(cmdset_f.duplicates)
self.assertEqual(len(cmdset_f.commands), 4)
a.priority = 0
b.priority = 0
c.priority = 0
d.priority = 0
c.duplicates = True
cmdset_f = d + b + c + a # two last mergers duplicates=True
self.assertEqual(len(cmdset_f.commands), 10)