mirror of
https://github.com/evennia/evennia.git
synced 2026-03-21 23:36:30 +01:00
Improvements to cmdsethandler/cmdset debugging
This commit is contained in:
parent
dda2493dba
commit
2eaa947ed4
9 changed files with 904 additions and 108 deletions
|
|
@ -5,7 +5,7 @@
|
|||
- new `drop:holds()` lock default to limit dropping nonsensical things. Access check
|
||||
defaults to True for backwards-compatibility in 0.9, will be False in 1.0
|
||||
|
||||
### Already in master
|
||||
### Evennia 0.95 (master)
|
||||
- `is_typeclass(obj (Object), exact (bool))` now defaults to exact=False
|
||||
- `py` command now reroutes stdout to output results in-game client. `py`
|
||||
without arguments starts a full interactive Python console.
|
||||
|
|
@ -74,7 +74,12 @@ without arguments starts a full interactive Python console.
|
|||
pagination (e.g. to create EvTables for every page instead of splittine one table)
|
||||
- Using `EvMore pagination`, dramatically improves performance of `spawn/list` and `scripts` listings
|
||||
(100x speed increase for displaying 1000+ prototypes/scripts).
|
||||
|
||||
- `EvMenu` now uses the more logically named `.ndb._evmenu` instead of `.ndb._menutree` to store itself.
|
||||
Both still work for backward compatibility, but `_menutree` is deprecated.
|
||||
- `EvMenu.msg(txt)` added as a central place to send text to the user, makes it easier to override.
|
||||
Default `EvMenu.msg` sends with OOB type="menu" for use with OOB and webclient pane-redirects.
|
||||
- New EvMenu templating system for quickly building simpler EvMenus without as much code.
|
||||
|
||||
|
||||
## Evennia 0.9 (2018-2019)
|
||||
|
||||
|
|
|
|||
|
|
@ -494,6 +494,11 @@ def get_and_merge_cmdsets(caller, session, account, obj, callertype, raw_string)
|
|||
cmdset = None
|
||||
for cset in (cset for cset in local_obj_cmdsets if cset):
|
||||
cset.duplicates = cset.old_duplicates
|
||||
# important - this syncs the CmdSetHandler's .current field with the
|
||||
# true current cmdset!
|
||||
if cmdset:
|
||||
caller.cmdset.current = cmdset
|
||||
|
||||
returnValue(cmdset)
|
||||
except ErrorReported:
|
||||
raise
|
||||
|
|
|
|||
|
|
@ -106,9 +106,9 @@ class CmdSet(object, metaclass=_CmdSetMeta):
|
|||
commands preference.
|
||||
|
||||
duplicates - determines what happens when two sets of equal
|
||||
priority merge. Default has the first of them in the
|
||||
priority merge (only). Defaults to None and has the first of them in the
|
||||
merger (i.e. A above) automatically taking
|
||||
precedence. But if allow_duplicates is true, the
|
||||
precedence. But if `duplicates` is true, the
|
||||
result will be a merger with more than one of each
|
||||
name match. This will usually lead to the account
|
||||
receiving a multiple-match error higher up the road,
|
||||
|
|
@ -119,6 +119,16 @@ class CmdSet(object, metaclass=_CmdSetMeta):
|
|||
select which ball to kick ... Allowing duplicates
|
||||
only makes sense for Union and Intersect, the setting
|
||||
is ignored for the other mergetypes.
|
||||
Note that the `duplicates` flag is *not* propagated in
|
||||
a cmdset merger. So `A + B = C` will result in
|
||||
a cmdset with duplicate commands, but C.duplicates will
|
||||
be `None`. For duplication to apply to a whole cmdset
|
||||
stack merge, _all_ cmdsets in the stack must have
|
||||
`.duplicates=True` set.
|
||||
Finally, if a final cmdset has `.duplicates=None` (the normal
|
||||
unless created alone with another value), the cmdhandler
|
||||
will assume True for object-based cmdsets and False for
|
||||
all other. This is usually the most intuitive outcome.
|
||||
|
||||
key_mergetype (dict) - allows the cmdset to define a unique
|
||||
mergetype for particular cmdsets. Format is
|
||||
|
|
@ -144,14 +154,27 @@ class CmdSet(object, metaclass=_CmdSetMeta):
|
|||
mergetype = "Union"
|
||||
priority = 0
|
||||
|
||||
# These flags, if set to None, will allow "pass-through" of lower-prio settings
|
||||
# of True/False. If set to True/False, will override lower-prio settings.
|
||||
# These flags, if set to None should be interpreted as 'I don't care' and,
|
||||
# will allow "pass-through" even of lower-prio cmdsets' explicitly True/False
|
||||
# options. If this is set to True/False however, priority matters.
|
||||
no_exits = None
|
||||
no_objs = None
|
||||
no_channels = None
|
||||
# same as above, but if left at None in the final merged set, the
|
||||
# cmdhandler will auto-assume True for Objects and stay False for all
|
||||
# other entities.
|
||||
# The .duplicates setting does not propagate and since duplicates can only happen
|
||||
# on same-prio cmdsets, there is no concept of passthrough on `None`.
|
||||
# The merger of two cmdsets always return in a cmdset with `duplicates=None`
|
||||
# (even if the result may have duplicated commands).
|
||||
# If a final cmdset has `duplicates=None` (normal, unless the cmdset is
|
||||
# created on its own with the flag set), the cmdhandler will auto-assume it to be
|
||||
# True for Object-based cmdsets and stay None/False for all other entities.
|
||||
#
|
||||
# Example:
|
||||
# A and C has .duplicates=True, B has .duplicates=None (or False)
|
||||
# B + A = BA, where BA will have duplicate cmds, but BA.duplicates = None
|
||||
# BA + C = BAC, where BAC will have more duplication, but BAC.duplicates = None
|
||||
#
|
||||
# Basically, for the `.duplicate` setting to survive throughout a
|
||||
# merge-stack, every cmdset in the stack must have `duplicates` set explicitly.
|
||||
duplicates = None
|
||||
|
||||
permanent = False
|
||||
|
|
@ -334,7 +357,15 @@ class CmdSet(object, metaclass=_CmdSetMeta):
|
|||
commands (str): Representation of commands in Cmdset.
|
||||
|
||||
"""
|
||||
return ", ".join([str(cmd) for cmd in sorted(self.commands, key=lambda o: o.key)])
|
||||
perm = "perm" if self.permanent else "non-perm"
|
||||
options = ", ".join([
|
||||
"{}:{}".format(opt, "T" if getattr(self, opt) else "F")
|
||||
for opt in ("no_exits", "no_objs", "no_channels", "duplicates")
|
||||
if getattr(self, opt) is not None
|
||||
])
|
||||
options = (", " + options) if options else ""
|
||||
return f"<CmdSet {self.key}, {self.mergetype}, {perm}, prio {self.priority}{options}>: " + ", ".join(
|
||||
[str(cmd) for cmd in sorted(self.commands, key=lambda o: o.key)])
|
||||
|
||||
def __iter__(self):
|
||||
"""
|
||||
|
|
@ -401,12 +432,15 @@ class CmdSet(object, metaclass=_CmdSetMeta):
|
|||
|
||||
# pass through options whenever they are set, unless the merging or higher-prio
|
||||
# set changes the setting (i.e. has a non-None value). We don't pass through
|
||||
# the duplicates setting; that is per-merge
|
||||
# the duplicates setting; that is per-merge; the resulting .duplicates value
|
||||
# is always None (so merging cmdsets must all have explicit values if wanting
|
||||
# to cause duplicates).
|
||||
cmdset_c.no_channels = (
|
||||
self.no_channels if cmdset_a.no_channels is None else cmdset_a.no_channels
|
||||
)
|
||||
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 = None
|
||||
|
||||
else:
|
||||
# B higher priority than A
|
||||
|
|
@ -428,12 +462,15 @@ class CmdSet(object, metaclass=_CmdSetMeta):
|
|||
|
||||
# pass through options whenever they are set, unless the higher-prio
|
||||
# set changes the setting (i.e. has a non-None value). We don't pass through
|
||||
# the duplicates setting; that is per-merge
|
||||
# the duplicates setting; that is per-merge; the resulting .duplicates value#
|
||||
# is always None (so merging cmdsets must all have explicit values if wanting
|
||||
# to cause duplicates).
|
||||
cmdset_c.no_channels = (
|
||||
cmdset_a.no_channels if self.no_channels is None else self.no_channels
|
||||
)
|
||||
cmdset_c.no_exits = cmdset_a.no_exits if self.no_exits is None else self.no_exits
|
||||
cmdset_c.no_objs = cmdset_a.no_objs if self.no_objs is None else self.no_objs
|
||||
cmdset_c.duplicates = None
|
||||
|
||||
# we store actual_mergetype since key_mergetypes
|
||||
# might be different from the main mergetype.
|
||||
|
|
|
|||
|
|
@ -293,7 +293,10 @@ class CmdSetHandler(object):
|
|||
|
||||
# the id of the "merged" current cmdset for easy access.
|
||||
self.key = None
|
||||
# this holds the "merged" current command set
|
||||
# this holds the "merged" current command set. Note that while the .update
|
||||
# method updates this field in order to have it synced when operating on
|
||||
# cmdsets in-code, when the game runs, this field is kept up-to-date by
|
||||
# the cmdsethandler's get_and_merge_cmdsets!
|
||||
self.current = None
|
||||
# this holds a history of CommandSets
|
||||
self.cmdset_stack = [_EmptyCmdSet(cmdsetobj=self.obj)]
|
||||
|
|
@ -311,27 +314,13 @@ class CmdSetHandler(object):
|
|||
Display current commands
|
||||
"""
|
||||
|
||||
string = ""
|
||||
strings = ["<CmdSetHandler> stack:"]
|
||||
mergelist = []
|
||||
if len(self.cmdset_stack) > 1:
|
||||
# We have more than one cmdset in stack; list them all
|
||||
for snum, cmdset in enumerate(self.cmdset_stack):
|
||||
mergetype = self.mergetype_stack[snum]
|
||||
permstring = "non-perm"
|
||||
if cmdset.permanent:
|
||||
permstring = "perm"
|
||||
if mergetype != cmdset.mergetype:
|
||||
mergetype = "%s^" % (mergetype)
|
||||
string += "\n %i: <%s (%s, prio %i, %s)>: %s" % (
|
||||
snum,
|
||||
cmdset.key,
|
||||
mergetype,
|
||||
cmdset.priority,
|
||||
permstring,
|
||||
cmdset,
|
||||
)
|
||||
mergelist.append(str(snum))
|
||||
string += "\n"
|
||||
mergelist.append(str(snum + 1))
|
||||
strings.append(f" {snum + 1}: {cmdset}")
|
||||
|
||||
# Display the currently active cmdset, limited by self.obj's permissions
|
||||
mergetype = self.mergetype_stack[-1]
|
||||
|
|
@ -339,27 +328,15 @@ class CmdSetHandler(object):
|
|||
merged_on = self.cmdset_stack[-2].key
|
||||
mergetype = _("custom {mergetype} on cmdset '{cmdset}'")
|
||||
mergetype = mergetype.format(mergetype=mergetype, cmdset=merged_on)
|
||||
|
||||
if mergelist:
|
||||
tmpstring = _(" <Merged {mergelist} {mergetype}, prio {prio}>: {current}")
|
||||
string += tmpstring.format(
|
||||
mergelist="+".join(mergelist),
|
||||
mergetype=mergetype,
|
||||
prio=self.current.priority,
|
||||
current=self.current,
|
||||
)
|
||||
# current is a result of mergers
|
||||
mergelist="+".join(mergelist)
|
||||
strings.append(f" <Merged {mergelist}>: {self.current}")
|
||||
else:
|
||||
permstring = "non-perm"
|
||||
if self.current.permanent:
|
||||
permstring = "perm"
|
||||
tmpstring = _(" <{key} ({mergetype}, prio {prio}, {permstring})>:\n {keylist}")
|
||||
string += tmpstring.format(
|
||||
key=self.current.key,
|
||||
mergetype=mergetype,
|
||||
prio=self.current.priority,
|
||||
permstring=permstring,
|
||||
keylist=", ".join(cmd.key for cmd in sorted(self.current, key=lambda o: o.key)),
|
||||
)
|
||||
return string.strip()
|
||||
# current is a single cmdset
|
||||
strings.append(" " + str(self.current))
|
||||
return "\n".join(strings).rstrip()
|
||||
|
||||
def _import_cmdset(self, cmdset_path, emit_to_obj=None):
|
||||
"""
|
||||
|
|
@ -381,12 +358,22 @@ class CmdSetHandler(object):
|
|||
def update(self, init_mode=False):
|
||||
"""
|
||||
Re-adds all sets in the handler to have an updated current
|
||||
set.
|
||||
|
||||
Args:
|
||||
init_mode (bool, optional): Used automatically right after
|
||||
this handler was created; it imports all permanent cmdsets
|
||||
from the database.
|
||||
|
||||
Notes:
|
||||
This method is necessary in order to always have a `.current`
|
||||
cmdset when working with the cmdsethandler in code. But the
|
||||
CmdSetHandler doesn't (cannot) consider external cmdsets and game
|
||||
state. This means that the .current calculated from this method
|
||||
will likely not match the true current cmdset as determined at
|
||||
run-time by `cmdhandler.get_and_merge_cmdsets()`. So in a running
|
||||
game the responsibility of keeping `.current` upt-to-date belongs
|
||||
to the central `cmdhandler.get_and_merge_cmdsets()`!
|
||||
|
||||
"""
|
||||
if init_mode:
|
||||
# reimport all permanent cmdsets
|
||||
|
|
|
|||
|
|
@ -2429,13 +2429,13 @@ class CmdExamine(ObjManipCommand):
|
|||
)
|
||||
return output
|
||||
|
||||
def format_output(self, obj, avail_cmdset):
|
||||
def format_output(self, obj, current_cmdset):
|
||||
"""
|
||||
Helper function that creates a nice report about an object.
|
||||
|
||||
Args:
|
||||
obj (any): Object to analyze.
|
||||
avail_cmdset (CmdSet): Current cmdset for object.
|
||||
current_cmdset (CmdSet): Current cmdset for object.
|
||||
|
||||
Returns:
|
||||
str: The formatted string.
|
||||
|
|
@ -2513,15 +2513,36 @@ class CmdExamine(ObjManipCommand):
|
|||
# cmdsets
|
||||
if not (len(obj.cmdset.all()) == 1 and obj.cmdset.current.key == "_EMPTY_CMDSET"):
|
||||
# all() returns a 'stack', so make a copy to sort.
|
||||
|
||||
def _format_options(cmdset):
|
||||
"""helper for cmdset-option display"""
|
||||
def _truefalse(string, value):
|
||||
if value is None:
|
||||
return ""
|
||||
if value:
|
||||
return f"{string}: T"
|
||||
return f"{string}: F"
|
||||
options = ", ".join(
|
||||
_truefalse(opt, getattr(cmdset, opt))
|
||||
for opt in ("no_exits", "no_objs", "no_channels", "duplicates")
|
||||
if getattr(cmdset, opt) is not None
|
||||
)
|
||||
options = ", " + options if options else ""
|
||||
return options
|
||||
|
||||
# cmdset stored on us
|
||||
stored_cmdsets = sorted(obj.cmdset.all(), key=lambda x: x.priority, reverse=True)
|
||||
output["Stored Cmdset(s)"] = "\n " + "\n ".join(
|
||||
f"{cmdset.path} [{cmdset.key}] ({cmdset.mergetype}, prio {cmdset.priority})"
|
||||
for cmdset in stored_cmdsets
|
||||
if cmdset.key != "_EMPTY_CMDSET"
|
||||
)
|
||||
stored = []
|
||||
for cmdset in stored_cmdsets:
|
||||
if cmdset.key == "_EMPTY_CMDSET":
|
||||
continue
|
||||
options = _format_options(cmdset)
|
||||
stored.append(
|
||||
f"{cmdset.path} [{cmdset.key}] ({cmdset.mergetype}, prio {cmdset.priority}{options})")
|
||||
output["Stored Cmdset(s)"] = "\n " + "\n ".join(stored)
|
||||
|
||||
# this gets all components of the currently merged set
|
||||
all_cmdsets = [(cmdset.key, cmdset) for cmdset in avail_cmdset.merged_from]
|
||||
all_cmdsets = [(cmdset.key, cmdset) for cmdset in current_cmdset.merged_from]
|
||||
# we always at least try to add account- and session sets since these are ignored
|
||||
# if we merge on the object level.
|
||||
if hasattr(obj, "account") and obj.account:
|
||||
|
|
@ -2551,15 +2572,24 @@ class CmdExamine(ObjManipCommand):
|
|||
pass
|
||||
all_cmdsets = [cmdset for cmdset in dict(all_cmdsets).values()]
|
||||
all_cmdsets.sort(key=lambda x: x.priority, reverse=True)
|
||||
output["Merged Cmdset(s)"] = "\n " + "\n ".join(
|
||||
f"{cmdset.path} [{cmdset.key}] ({cmdset.mergetype} prio {cmdset.priority})"
|
||||
for cmdset in all_cmdsets
|
||||
)
|
||||
# list the commands available to this object
|
||||
avail_cmdset = sorted([cmd.key for cmd in avail_cmdset if cmd.access(obj, "cmd")])
|
||||
|
||||
cmdsetstr = "\n" + utils.fill(", ".join(avail_cmdset), indent=2)
|
||||
# the resulting merged cmdset
|
||||
options = _format_options(current_cmdset)
|
||||
merged = [
|
||||
f"<Current merged cmdset> ({current_cmdset.mergetype} prio {current_cmdset.priority}{options})"]
|
||||
|
||||
# the merge stack
|
||||
for cmdset in all_cmdsets:
|
||||
options = _format_options(cmdset)
|
||||
merged.append(
|
||||
f"{cmdset.path} [{cmdset.key}] ({cmdset.mergetype} prio {cmdset.priority}{options})")
|
||||
output["Merged Cmdset(s)"] = "\n " + "\n ".join(merged)
|
||||
|
||||
# list the commands available to this object
|
||||
current_commands = sorted([cmd.key for cmd in current_cmdset if cmd.access(obj, "cmd")])
|
||||
cmdsetstr = "\n" + utils.fill(", ".join(current_commands), indent=2)
|
||||
output[f"Commands available to {obj.key} (result of Merged CmdSets)"] = str(cmdsetstr)
|
||||
|
||||
# scripts
|
||||
if hasattr(obj, "scripts") and hasattr(obj.scripts, "all") and obj.scripts.all():
|
||||
output["Scripts"] = "\n " + f"{obj.scripts}"
|
||||
|
|
|
|||
|
|
@ -971,7 +971,8 @@ class TestBuilding(CommandTest):
|
|||
self.call(building.CmdSetHome(), "Obj = Room2", "Home location of Obj was set to Room")
|
||||
|
||||
def test_list_cmdsets(self):
|
||||
self.call(building.CmdListCmdSets(), "", "<DefaultCharacter (Union, prio 0, perm)>:")
|
||||
self.call(building.CmdListCmdSets(), "",
|
||||
"<CmdSetHandler> stack:\n <CmdSet DefaultCharacter, Union, perm, prio 0>:")
|
||||
self.call(building.CmdListCmdSets(), "NotFound", "Could not find 'NotFound'")
|
||||
|
||||
def test_typeclass(self):
|
||||
|
|
|
|||
|
|
@ -194,27 +194,71 @@ class TestCmdSetMergers(TestCase):
|
|||
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"
|
||||
|
||||
class TestOptionTransferTrue(TestCase):
|
||||
"""
|
||||
Test cmdset-merge transfer of the cmdset-special options
|
||||
(no_exits/channels/objs/duplicates etc)
|
||||
|
||||
cmdset A has all True options
|
||||
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.cmdset_a = _CmdSetA()
|
||||
self.cmdset_b = _CmdSetB()
|
||||
self.cmdset_c = _CmdSetC()
|
||||
self.cmdset_d = _CmdSetD()
|
||||
self.cmdset_a.priority = 0
|
||||
self.cmdset_b.priority = 0
|
||||
self.cmdset_c.priority = 0
|
||||
self.cmdset_d.priority = 0
|
||||
self.cmdset_a.no_exits = True
|
||||
self.cmdset_a.no_objs = True
|
||||
self.cmdset_a.no_channels = True
|
||||
self.cmdset_a.duplicates = True
|
||||
|
||||
def test_option_transfer__reverse_sameprio_passthrough(self):
|
||||
"""
|
||||
A has all True options, merges last (normal reverse merge), same prio.
|
||||
The options should pass through to F since none of the other cmdsets
|
||||
care to change the setting from their default None.
|
||||
|
||||
Since A.duplicates = True, the final result is an union of duplicate
|
||||
pairs (8 commands total).
|
||||
|
||||
"""
|
||||
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
|
||||
# the options should pass through since none of the other cmdsets care
|
||||
# to change the setting from None.
|
||||
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.assertIsNone(cmdset_f.duplicates)
|
||||
self.assertEqual(len(cmdset_f.commands), 8)
|
||||
|
||||
def test_option_transfer__forward_sameprio_passthrough(self):
|
||||
"""
|
||||
A has all True options, merges first (forward merge), same prio. This
|
||||
should pass those options through since the other all have options set
|
||||
to None. The exception is `duplicates` since that is determined by
|
||||
the two last mergers in the chain both being True.
|
||||
|
||||
"""
|
||||
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
|
||||
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.assertFalse(cmdset_f.duplicates)
|
||||
self.assertIsNone(cmdset_f.duplicates)
|
||||
self.assertEqual(len(cmdset_f.commands), 4)
|
||||
|
||||
def test_option_transfer__reverse_highprio_passthrough(self):
|
||||
"""
|
||||
A has all True options, merges last (normal reverse merge) with the
|
||||
highest prio. This should also pass through.
|
||||
"""
|
||||
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
|
||||
|
|
@ -223,14 +267,35 @@ class TestCmdSetMergers(TestCase):
|
|||
self.assertTrue(cmdset_f.no_exits)
|
||||
self.assertTrue(cmdset_f.no_objs)
|
||||
self.assertTrue(cmdset_f.no_channels)
|
||||
self.assertTrue(cmdset_f.duplicates)
|
||||
self.assertIsNone(cmdset_f.duplicates)
|
||||
self.assertEqual(len(cmdset_f.commands), 4)
|
||||
|
||||
def test_option_transfer__forward_highprio_passthrough(self):
|
||||
"""
|
||||
A has all True options, merges first (forward merge). This is a bit
|
||||
synthetic since it will never happen in practice, but logic should
|
||||
still make it pass through.
|
||||
"""
|
||||
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 = a + b + c + d # forward, A top priority. This never happens in practice.
|
||||
self.assertTrue(cmdset_f.no_exits)
|
||||
self.assertTrue(cmdset_f.no_objs)
|
||||
self.assertTrue(cmdset_f.no_channels)
|
||||
self.assertTrue(cmdset_f.duplicates)
|
||||
self.assertIsNone(cmdset_f.duplicates)
|
||||
self.assertEqual(len(cmdset_f.commands), 4)
|
||||
|
||||
def test_option_transfer__reverse_lowprio_passthrough(self):
|
||||
"""
|
||||
A has all True options, merges last (normal reverse merge) with the lowest
|
||||
prio. This never happens (it would always merge first) but logic should hold
|
||||
and pass through since the other cmdsets have None.
|
||||
|
||||
"""
|
||||
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
|
||||
a.priority = -1
|
||||
b.priority = 0
|
||||
c.priority = 1
|
||||
|
|
@ -239,32 +304,678 @@ class TestCmdSetMergers(TestCase):
|
|||
self.assertTrue(cmdset_f.no_exits)
|
||||
self.assertTrue(cmdset_f.no_objs)
|
||||
self.assertTrue(cmdset_f.no_channels)
|
||||
self.assertFalse(cmdset_f.duplicates)
|
||||
self.assertIsNone(cmdset_f.duplicates)
|
||||
self.assertEqual(len(cmdset_f.commands), 4)
|
||||
|
||||
def test_option_transfer__forward_lowprio_passthrough(self):
|
||||
"""
|
||||
A has all True options, merges first (forward merge) with lowest prio. This
|
||||
is the normal behavior for a low-prio cmdset. Passthrough should happen.
|
||||
"""
|
||||
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
|
||||
a.priority = -1
|
||||
b.priority = 0
|
||||
c.priority = 1
|
||||
d.priority = 2
|
||||
cmdset_f = a + b + c + d # forward, A low prio
|
||||
self.assertTrue(cmdset_f.no_exits)
|
||||
self.assertTrue(cmdset_f.no_objs)
|
||||
self.assertTrue(cmdset_f.no_channels)
|
||||
self.assertFalse(cmdset_f.duplicates)
|
||||
self.assertIsNone(cmdset_f.duplicates)
|
||||
self.assertEqual(len(cmdset_f.commands), 4)
|
||||
|
||||
def test_option_transfer__reverse_highprio_block_passthrough(self):
|
||||
"""
|
||||
A has all True options, other cmdsets has False. A merges last with high
|
||||
prio. A should retain its option values and override the others
|
||||
|
||||
"""
|
||||
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
|
||||
c.no_exits = False
|
||||
b.no_objs = False
|
||||
d.duplicates = False
|
||||
# higher-prio sets will change the option up the chain
|
||||
cmdset_f = d + c + b + a # reverse, high prio
|
||||
self.assertTrue(cmdset_f.no_exits)
|
||||
self.assertTrue(cmdset_f.no_objs)
|
||||
self.assertTrue(cmdset_f.no_channels)
|
||||
self.assertIsNone(cmdset_f.duplicates)
|
||||
self.assertEqual(len(cmdset_f.commands), 4)
|
||||
|
||||
def test_option_transfer__forward_highprio_block_passthrough(self):
|
||||
"""
|
||||
A has all True options, other cmdsets has False. A merges last with high
|
||||
prio. This situation should never happen, but logic should hold - the highest
|
||||
prio's options should survive the merge process.
|
||||
|
||||
"""
|
||||
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
|
||||
c.no_exits = False
|
||||
b.no_channels = False
|
||||
b.no_objs = False
|
||||
d.duplicates = False
|
||||
# higher-prio sets will change the option up the chain
|
||||
cmdset_f = a + b + c + d # forward, high prio, never happens
|
||||
self.assertTrue(cmdset_f.no_exits)
|
||||
self.assertTrue(cmdset_f.no_objs)
|
||||
self.assertTrue(cmdset_f.no_channels)
|
||||
self.assertIsNone(cmdset_f.duplicates)
|
||||
self.assertEqual(len(cmdset_f.commands), 4)
|
||||
|
||||
def test_option_transfer__forward_lowprio_block(self):
|
||||
"""
|
||||
A has all True options, other cmdsets has False. A merges last with low
|
||||
prio. This should result in its values being blocked and come out False.
|
||||
|
||||
"""
|
||||
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
|
||||
a.priority = -1
|
||||
b.priority = 0
|
||||
c.priority = 1
|
||||
d.priority = 2
|
||||
c.no_exits = False
|
||||
c.no_channels = False
|
||||
b.no_objs = False
|
||||
d.duplicates = False
|
||||
# higher-prio sets will change the option up the chain
|
||||
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.assertIsNone(cmdset_f.duplicates)
|
||||
self.assertEqual(len(cmdset_f.commands), 4)
|
||||
|
||||
def test_option_transfer__forward_lowprio_block_partial(self):
|
||||
"""
|
||||
A has all True options, other cmdsets has False excet C which has a None
|
||||
for `no_channels`. A merges last with low
|
||||
prio. This should result in its values being blocked and come out False
|
||||
except for no_channels which passes through.
|
||||
|
||||
"""
|
||||
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
|
||||
a.priority = -1
|
||||
b.priority = 0
|
||||
c.priority = 1
|
||||
d.priority = 2
|
||||
c.no_exits = False
|
||||
c.no_channels = None # passthrough
|
||||
b.no_objs = False
|
||||
d.duplicates = False
|
||||
# higher-prio sets will change the option up the chain
|
||||
cmdset_f = a + b + c + d # forward, A low prio
|
||||
self.assertFalse(cmdset_f.no_exits)
|
||||
self.assertFalse(cmdset_f.no_objs)
|
||||
self.assertTrue(cmdset_f.no_channels)
|
||||
self.assertFalse(cmdset_f.duplicates)
|
||||
self.assertIsNone(cmdset_f.duplicates)
|
||||
self.assertEqual(len(cmdset_f.commands), 4)
|
||||
a.priority = 0
|
||||
b.priority = 0
|
||||
|
||||
def test_option_transfer__reverse_highprio_sameprio_order_last(self):
|
||||
"""
|
||||
A has all True options and highest prio, D has False and lowest prio,
|
||||
others are passthrough. B has the same prio as A, with passthrough.
|
||||
|
||||
Since A is merged last, this should give prio to A's options
|
||||
"""
|
||||
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
|
||||
a.priority = 2
|
||||
b.priority = 2
|
||||
c.priority = 0
|
||||
d.priority = 0
|
||||
d.priority = -1
|
||||
d.no_channels = False
|
||||
d.no_exits = False
|
||||
d.no_objs = None
|
||||
d.duplicates = False
|
||||
# higher-prio sets will change the option up the chain
|
||||
cmdset_f = d + c + b + a # reverse, A same prio, merged after b
|
||||
self.assertTrue(cmdset_f.no_exits)
|
||||
self.assertTrue(cmdset_f.no_objs)
|
||||
self.assertTrue(cmdset_f.no_channels)
|
||||
self.assertIsNone(cmdset_f.duplicates)
|
||||
self.assertEqual(len(cmdset_f.commands), 8)
|
||||
|
||||
def test_option_transfer__reverse_highprio_sameprio_order_first(self):
|
||||
"""
|
||||
A has all True options and highest prio, D has False and lowest prio,
|
||||
others are passthrough. B has the same prio as A, with passthrough.
|
||||
|
||||
While B, with None-values, is merged after A, A's options should have
|
||||
replaced those of D at that point, and since B has passthrough the
|
||||
final result should contain A's True options.
|
||||
|
||||
Note that despite A having duplicates=True, there is no duplication in
|
||||
the DB + A merger since they have different priorities.
|
||||
|
||||
"""
|
||||
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
|
||||
a.priority = 2
|
||||
b.priority = 2
|
||||
c.priority = 0
|
||||
d.priority = -1
|
||||
d.no_channels = False
|
||||
d.no_exits = False
|
||||
d.no_objs = False
|
||||
d.duplicates = False
|
||||
# higher-prio sets will change the option up the chain
|
||||
cmdset_f = d + c + a + b # reverse, A same prio, merged before b
|
||||
self.assertTrue(cmdset_f.no_exits)
|
||||
self.assertTrue(cmdset_f.no_objs)
|
||||
self.assertTrue(cmdset_f.no_channels)
|
||||
self.assertIsNone(cmdset_f.duplicates)
|
||||
self.assertEqual(len(cmdset_f.commands), 4)
|
||||
|
||||
def test_option_transfer__reverse_lowprio_block(self):
|
||||
"""
|
||||
A has all True options, other cmdsets has False. A merges last with low
|
||||
prio. This usually doesn't happen- it should merge last. But logic should
|
||||
hold and the low-prio cmdset's values should be blocked and come out False.
|
||||
|
||||
"""
|
||||
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
|
||||
a.priority = -1
|
||||
b.priority = 0
|
||||
c.priority = 1
|
||||
d.priority = 2
|
||||
c.no_exits = False
|
||||
d.no_channels = False
|
||||
b.no_objs = False
|
||||
d.duplicates = False
|
||||
# higher-prio sets will change the option up the chain
|
||||
cmdset_f = d + c + b + a # reverse, A low prio, never happens
|
||||
self.assertFalse(cmdset_f.no_exits)
|
||||
self.assertFalse(cmdset_f.no_objs)
|
||||
self.assertFalse(cmdset_f.no_channels)
|
||||
self.assertIsNone(cmdset_f.duplicates)
|
||||
self.assertEqual(len(cmdset_f.commands), 4)
|
||||
|
||||
|
||||
class TestOptionTransferFalse(TestCase):
|
||||
"""
|
||||
Test cmdset-merge transfer of the cmdset-special options
|
||||
(no_exits/channels/objs/duplicates etc)
|
||||
|
||||
cmdset A has all False options
|
||||
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.cmdset_a = _CmdSetA()
|
||||
self.cmdset_b = _CmdSetB()
|
||||
self.cmdset_c = _CmdSetC()
|
||||
self.cmdset_d = _CmdSetD()
|
||||
self.cmdset_a.priority = 0
|
||||
self.cmdset_b.priority = 0
|
||||
self.cmdset_c.priority = 0
|
||||
self.cmdset_d.priority = 0
|
||||
self.cmdset_a.no_exits = False
|
||||
self.cmdset_a.no_objs = False
|
||||
self.cmdset_a.no_channels = False
|
||||
self.cmdset_a.duplicates = False
|
||||
|
||||
def test_option_transfer__reverse_sameprio_passthrough(self):
|
||||
"""
|
||||
A has all False options, merges last (normal reverse merge), same prio.
|
||||
The options should pass through to F since none of the other cmdsets
|
||||
care to change the setting from their default None.
|
||||
|
||||
Since A has duplicates=False, the result is a unique union of 4 cmds.
|
||||
"""
|
||||
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
|
||||
cmdset_f = d + c + b + a # reverse, same-prio
|
||||
self.assertFalse(cmdset_f.no_exits)
|
||||
self.assertFalse(cmdset_f.no_objs)
|
||||
self.assertFalse(cmdset_f.no_channels)
|
||||
self.assertIsNone(cmdset_f.duplicates)
|
||||
self.assertEqual(len(cmdset_f.commands), 4)
|
||||
|
||||
def test_option_transfer__forward_sameprio_passthrough(self):
|
||||
"""
|
||||
A has all False options, merges first (forward merge), same prio. This
|
||||
should pass those options through since the other all have options set
|
||||
to None. The exception is `duplicates` since that is determined by
|
||||
the two last mergers in the chain both being .
|
||||
|
||||
"""
|
||||
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
|
||||
cmdset_f = a + b + c + d # forward, same-prio
|
||||
self.assertFalse(cmdset_f.no_exits)
|
||||
self.assertFalse(cmdset_f.no_objs)
|
||||
self.assertFalse(cmdset_f.no_channels)
|
||||
self.assertIsNone(cmdset_f.duplicates)
|
||||
self.assertEqual(len(cmdset_f.commands), 4)
|
||||
|
||||
def test_option_transfer__reverse_highprio_passthrough(self):
|
||||
"""
|
||||
A has all False options, merges last (normal reverse merge) with the
|
||||
highest prio. This should also pass through.
|
||||
"""
|
||||
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 # reverse, A top priority
|
||||
self.assertFalse(cmdset_f.no_exits)
|
||||
self.assertFalse(cmdset_f.no_objs)
|
||||
self.assertFalse(cmdset_f.no_channels)
|
||||
self.assertIsNone(cmdset_f.duplicates)
|
||||
self.assertEqual(len(cmdset_f.commands), 4)
|
||||
|
||||
def test_option_transfer__forward_highprio_passthrough(self):
|
||||
"""
|
||||
A has all False options, merges first (forward merge). This is a bit
|
||||
synthetic since it will never happen in practice, but logic should
|
||||
still make it pass through.
|
||||
"""
|
||||
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 = a + b + c + d # forward, A top priority. This never happens in practice.
|
||||
self.assertFalse(cmdset_f.no_exits)
|
||||
self.assertFalse(cmdset_f.no_objs)
|
||||
self.assertFalse(cmdset_f.no_channels)
|
||||
self.assertIsNone(cmdset_f.duplicates)
|
||||
self.assertEqual(len(cmdset_f.commands), 4)
|
||||
|
||||
def test_option_transfer__reverse_lowprio_passthrough(self):
|
||||
"""
|
||||
A has all False options, merges last (normal reverse merge) with the lowest
|
||||
prio. This never happens (it would always merge first) but logic should hold
|
||||
and pass through since the other cmdsets have None.
|
||||
|
||||
"""
|
||||
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
|
||||
a.priority = -1
|
||||
b.priority = 0
|
||||
c.priority = 1
|
||||
d.priority = 2
|
||||
cmdset_f = d + c + b + a # reverse, A low prio. This never happens in practice.
|
||||
self.assertFalse(cmdset_f.no_exits)
|
||||
self.assertFalse(cmdset_f.no_objs)
|
||||
self.assertFalse(cmdset_f.no_channels)
|
||||
self.assertIsNone(cmdset_f.duplicates)
|
||||
self.assertEqual(len(cmdset_f.commands), 4)
|
||||
|
||||
def test_option_transfer__forward_lowprio_passthrough(self):
|
||||
"""
|
||||
A has all False options, merges first (forward merge) with lowest prio. This
|
||||
is the normal behavior for a low-prio cmdset. Passthrough should happen.
|
||||
"""
|
||||
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
|
||||
a.priority = -1
|
||||
b.priority = 0
|
||||
c.priority = 1
|
||||
d.priority = 2
|
||||
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.assertIsNone(cmdset_f.duplicates)
|
||||
self.assertEqual(len(cmdset_f.commands), 4)
|
||||
|
||||
def test_option_transfer__reverse_highprio_block_passthrough(self):
|
||||
"""
|
||||
A has all False options, other cmdsets has True. A merges last with high
|
||||
prio. A should retain its option values and override the others
|
||||
|
||||
"""
|
||||
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
|
||||
c.no_exits = True
|
||||
b.no_objs = True
|
||||
d.duplicates = True
|
||||
# higher-prio sets will change the option up the chain
|
||||
cmdset_f = d + c + b + a # reverse, high prio
|
||||
self.assertFalse(cmdset_f.no_exits)
|
||||
self.assertFalse(cmdset_f.no_objs)
|
||||
self.assertFalse(cmdset_f.no_channels)
|
||||
self.assertIsNone(cmdset_f.duplicates)
|
||||
self.assertEqual(len(cmdset_f.commands), 4)
|
||||
|
||||
def test_option_transfer__forward_highprio_block_passthrough(self):
|
||||
"""
|
||||
A has all False options, other cmdsets has True. A merges last with high
|
||||
prio. This situation should never happen, but logic should hold - the highest
|
||||
prio's options should survive the merge process.
|
||||
|
||||
"""
|
||||
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
|
||||
c.no_exits = True
|
||||
b.no_channels = True
|
||||
b.no_objs = True
|
||||
d.duplicates = True
|
||||
# higher-prio sets will change the option up the chain
|
||||
cmdset_f = a + b + c + d # forward, high prio, never happens
|
||||
self.assertFalse(cmdset_f.no_exits)
|
||||
self.assertFalse(cmdset_f.no_objs)
|
||||
self.assertFalse(cmdset_f.no_channels)
|
||||
self.assertIsNone(cmdset_f.duplicates)
|
||||
self.assertEqual(len(cmdset_f.commands), 4)
|
||||
|
||||
def test_option_transfer__forward_lowprio_block(self):
|
||||
"""
|
||||
A has all False options, other cmdsets has True. A merges last with low
|
||||
prio. This should result in its values being blocked and come out False.
|
||||
|
||||
"""
|
||||
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
|
||||
a.priority = -1
|
||||
b.priority = 0
|
||||
c.priority = 1
|
||||
d.priority = 2
|
||||
c.no_exits = True
|
||||
c.no_channels = True
|
||||
b.no_objs = True
|
||||
d.duplicates = True
|
||||
# higher-prio sets will change the option up the chain
|
||||
cmdset_f = a + b + c + d # forward, A low prio
|
||||
self.assertTrue(cmdset_f.no_exits)
|
||||
self.assertTrue(cmdset_f.no_objs)
|
||||
self.assertTrue(cmdset_f.no_channels)
|
||||
self.assertIsNone(cmdset_f.duplicates)
|
||||
self.assertEqual(len(cmdset_f.commands), 4)
|
||||
|
||||
def test_option_transfer__forward_lowprio_block_partial(self):
|
||||
"""
|
||||
A has all False options, other cmdsets has True excet C which has a None
|
||||
for `no_channels`. A merges last with low
|
||||
prio. This should result in its values being blocked and come out True
|
||||
except for no_channels which passes through.
|
||||
|
||||
"""
|
||||
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
|
||||
a.priority = -1
|
||||
b.priority = 0
|
||||
c.priority = 1
|
||||
d.priority = 2
|
||||
c.no_exits = True
|
||||
c.no_channels = None # passthrough
|
||||
b.no_objs = True
|
||||
d.duplicates = True
|
||||
# higher-prio sets will change the option up the chain
|
||||
cmdset_f = a + b + c + d # forward, A low prio
|
||||
self.assertTrue(cmdset_f.no_exits)
|
||||
self.assertTrue(cmdset_f.no_objs)
|
||||
self.assertFalse(cmdset_f.no_channels)
|
||||
self.assertIsNone(cmdset_f.duplicates)
|
||||
self.assertEqual(len(cmdset_f.commands), 4)
|
||||
|
||||
def test_option_transfer__reverse_sameprio_order_last(self):
|
||||
"""
|
||||
A has all False options and highest prio, D has True and lowest prio,
|
||||
others are passthrough. B has the same prio as A, with passthrough.
|
||||
|
||||
Since A is merged last, this should give prio to A's False options
|
||||
"""
|
||||
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
|
||||
a.priority = 2
|
||||
b.priority = 2
|
||||
c.priority = 0
|
||||
d.priority = -1
|
||||
d.no_channels = True
|
||||
d.no_exits = True
|
||||
d.no_objs = True
|
||||
d.duplicates = False
|
||||
# higher-prio sets will change the option up the chain
|
||||
cmdset_f = d + c + b + a # reverse, A high prio, merged after b
|
||||
self.assertFalse(cmdset_f.no_exits)
|
||||
self.assertFalse(cmdset_f.no_objs)
|
||||
self.assertFalse(cmdset_f.no_channels)
|
||||
self.assertIsNone(cmdset_f.duplicates)
|
||||
self.assertEqual(len(cmdset_f.commands), 4)
|
||||
|
||||
def test_option_transfer__reverse_sameprio_order_first(self):
|
||||
"""
|
||||
A has all False options and highest prio, D has True and lowest prio,
|
||||
others are passthrough. B has the same prio as A, with passthrough.
|
||||
|
||||
While B, with None-values, is merged after A, A's options should have
|
||||
replaced those of D at that point, and since B has passthrough the
|
||||
final result should contain A's False options.
|
||||
|
||||
"""
|
||||
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
|
||||
a.priority = 2
|
||||
b.priority = 2
|
||||
c.priority = 0
|
||||
d.priority = -1
|
||||
d.no_channels = True
|
||||
d.no_exits = True
|
||||
d.no_objs = True
|
||||
d.duplicates = False
|
||||
|
||||
# higher-prio sets will change the option up the chain
|
||||
cmdset_f = d + c + a + b # reverse, A high prio, merged before b
|
||||
self.assertFalse(cmdset_f.no_exits)
|
||||
self.assertFalse(cmdset_f.no_objs)
|
||||
self.assertFalse(cmdset_f.no_channels)
|
||||
self.assertIsNone(cmdset_f.duplicates)
|
||||
self.assertEqual(len(cmdset_f.commands), 4)
|
||||
|
||||
def test_option_transfer__reverse_lowprio_block(self):
|
||||
"""
|
||||
A has all False options, other cmdsets has True. A merges last with low
|
||||
prio. This usually doesn't happen- it should merge last. But logic should
|
||||
hold and the low-prio cmdset's values should be blocked and come out True.
|
||||
|
||||
"""
|
||||
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
|
||||
a.priority = -1
|
||||
b.priority = 0
|
||||
c.priority = 1
|
||||
d.priority = 2
|
||||
c.no_exits = True
|
||||
d.no_channels = True
|
||||
b.no_objs = True
|
||||
d.duplicates = True
|
||||
# higher-prio sets will change the option up the chain
|
||||
cmdset_f = d + c + b + a # reverse, A low prio, never happens
|
||||
self.assertTrue(cmdset_f.no_exits)
|
||||
self.assertTrue(cmdset_f.no_objs)
|
||||
self.assertTrue(cmdset_f.no_channels)
|
||||
self.assertIsNone(cmdset_f.duplicates)
|
||||
self.assertEqual(len(cmdset_f.commands), 4)
|
||||
|
||||
|
||||
class TestDuplicateBehavior(TestCase):
|
||||
"""
|
||||
Test behavior of .duplicate option, which is a bit special in that it
|
||||
doesn't propagate.
|
||||
|
||||
`A.duplicates=True` for all tests.
|
||||
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.cmdset_a = _CmdSetA()
|
||||
self.cmdset_b = _CmdSetB()
|
||||
self.cmdset_c = _CmdSetC()
|
||||
self.cmdset_d = _CmdSetD()
|
||||
self.cmdset_a.priority = 0
|
||||
self.cmdset_b.priority = 0
|
||||
self.cmdset_c.priority = 0
|
||||
self.cmdset_d.priority = 0
|
||||
self.cmdset_a.duplicates = True
|
||||
|
||||
def test_reverse_sameprio_duplicate(self):
|
||||
"""
|
||||
Test of `duplicates` transfer which does not propagate. Only
|
||||
A has duplicates=True.
|
||||
|
||||
D + B = DB (no duplication, DB.duplication=None)
|
||||
DB + C = DBC (no duplication, DBC.duplication=None)
|
||||
DBC + A = final (duplication, final.duplication=None)
|
||||
|
||||
"""
|
||||
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
|
||||
cmdset_f = d + b + c + a # two last mergers duplicates=True
|
||||
self.assertIsNone(cmdset_f.duplicates)
|
||||
self.assertEqual(len(cmdset_f.commands), 8)
|
||||
|
||||
def test_reverse_sameprio_duplicate(self):
|
||||
"""
|
||||
Test of `duplicates` transfer, which does not propagate.
|
||||
C.duplication=True
|
||||
|
||||
D + B = DB (no duplication, DB.duplication=None)
|
||||
DB + C = DBC (duplication, DBC.duplication=None)
|
||||
DBC + A = final (duplication, final.duplication=None)
|
||||
|
||||
"""
|
||||
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
|
||||
c.duplicates = True
|
||||
cmdset_f = d + b + c + a # two last mergers duplicates=True
|
||||
self.assertIsNone(cmdset_f.duplicates)
|
||||
self.assertEqual(len(cmdset_f.commands), 10)
|
||||
|
||||
def test_forward_sameprio_duplicate(self):
|
||||
"""
|
||||
Test of `duplicates` transfer which does not propagate.
|
||||
C.duplication=True, merges later than A
|
||||
|
||||
D + B = DB (no duplication, DB.duplication=None)
|
||||
DB + A = DBA (duplication, DBA.duplication=None)
|
||||
DBA + C = final (duplication, final.duplication=None)
|
||||
|
||||
"""
|
||||
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
|
||||
c.duplicates = True
|
||||
cmdset_f = d + b + a + c # two last mergers duplicates=True
|
||||
self.assertIsNone(cmdset_f.duplicates)
|
||||
self.assertEqual(len(cmdset_f.commands), 10)
|
||||
|
||||
def test_reverse_sameprio_duplicate_reverse(self):
|
||||
"""
|
||||
Test of `duplicates` transfer which does not propagate.
|
||||
C.duplication=False (explicit), merges before A. This behavior is the
|
||||
same as if C.duplication=None, since A merges later and takes
|
||||
precedence.
|
||||
|
||||
D + B = DB (no duplication, DB.duplication=None)
|
||||
DB + C = DBC (no duplication, DBC.duplication=None)
|
||||
DBC + A = final (duplication, final.duplication=None)
|
||||
|
||||
"""
|
||||
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
|
||||
c.duplicates = False
|
||||
cmdset_f = d + b + c + a # a merges last, takes precedence
|
||||
self.assertIsNone(cmdset_f.duplicates)
|
||||
self.assertEqual(len(cmdset_f.commands), 8)
|
||||
|
||||
def test_reverse_sameprio_duplicate_forward(self):
|
||||
"""
|
||||
Test of `duplicates` transfer which does not propagate.
|
||||
C.duplication=False (explicit), merges after A. This just means
|
||||
only A causes duplicates, earlier in the chain.
|
||||
|
||||
D + B = DB (no duplication, DB.duplication=None)
|
||||
DB + A = DBA (duplication, DBA.duplication=None)
|
||||
DBA + C = final (no duplication, final.duplication=None)
|
||||
|
||||
Note that DBA has 8 cmds due to A merging onto DB with duplication,
|
||||
but since C merges onto this with no duplication, the union will hold
|
||||
6 commands, since C has two commands that replaces the 4 duplicates
|
||||
with uniques copies from C.
|
||||
|
||||
"""
|
||||
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
|
||||
c.duplicates = False
|
||||
cmdset_f = d + b + a + c # a merges before c
|
||||
self.assertIsNone(cmdset_f.duplicates)
|
||||
self.assertEqual(len(cmdset_f.commands), 6)
|
||||
|
||||
|
||||
class TestOptionTransferReplace(TestCase):
|
||||
"""
|
||||
Test option transfer through more complex merge types.
|
||||
"""
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.cmdset_a = _CmdSetA()
|
||||
self.cmdset_b = _CmdSetB()
|
||||
self.cmdset_c = _CmdSetC()
|
||||
self.cmdset_d = _CmdSetD()
|
||||
self.cmdset_a.priority = 0
|
||||
self.cmdset_b.priority = 0
|
||||
self.cmdset_c.priority = 0
|
||||
self.cmdset_d.priority = 0
|
||||
self.cmdset_a.no_exits = True
|
||||
self.cmdset_a.no_objs = True
|
||||
self.cmdset_a.no_channels = True
|
||||
self.cmdset_a.duplicates = True
|
||||
|
||||
def test_option_transfer__replace_reverse_highprio(self):
|
||||
"""
|
||||
A has all options True and highest priority. C has them False and is
|
||||
Replace-type.
|
||||
|
||||
"""
|
||||
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
|
||||
a.priority = 2
|
||||
b.priority = 2
|
||||
c.priority = 0
|
||||
c.mergetype = "Replace"
|
||||
c.no_channels = False
|
||||
c.no_exits = False
|
||||
c.no_objs = False
|
||||
c.duplicates = False
|
||||
d.priority = -1
|
||||
|
||||
cmdset_f = d + c + b + a # reverse, A high prio, C Replace
|
||||
self.assertTrue(cmdset_f.no_exits)
|
||||
self.assertTrue(cmdset_f.no_objs)
|
||||
self.assertTrue(cmdset_f.no_channels)
|
||||
self.assertIsNone(cmdset_f.duplicates)
|
||||
self.assertEqual(len(cmdset_f.commands), 7)
|
||||
|
||||
def test_option_transfer__replace_reverse_highprio_from_false(self):
|
||||
"""
|
||||
Inverse of previous test: A has all options False and highest priority.
|
||||
C has them True and is Replace-type.
|
||||
|
||||
"""
|
||||
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
|
||||
a.no_exits = False
|
||||
a.no_objs = False
|
||||
a.no_channels = False
|
||||
a.duplicates = False
|
||||
|
||||
a.priority = 2
|
||||
b.priority = 2
|
||||
c.priority = 0
|
||||
c.mergetype = "Replace"
|
||||
c.no_channels = True
|
||||
c.no_exits = True
|
||||
c.no_objs = True
|
||||
c.duplicates = True
|
||||
d.priority = -1
|
||||
|
||||
cmdset_f = d + c + b + a # reverse, A high prio, C Replace
|
||||
self.assertFalse(cmdset_f.no_exits)
|
||||
self.assertFalse(cmdset_f.no_objs)
|
||||
self.assertFalse(cmdset_f.no_channels)
|
||||
self.assertIsNone(cmdset_f.duplicates)
|
||||
self.assertEqual(len(cmdset_f.commands), 4)
|
||||
|
||||
|
||||
# test cmdhandler functions
|
||||
|
||||
|
|
|
|||
|
|
@ -179,6 +179,7 @@ class DemoCommandSetRoom(CmdSet):
|
|||
key = "cmd_demo_cmdset_room"
|
||||
priority = 2
|
||||
no_exits = False
|
||||
no_objs = False
|
||||
|
||||
def at_cmdset_creation(self):
|
||||
from evennia import default_cmds
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ both one or two arguments interchangeably. It also accepts nodes
|
|||
that takes **kwargs.
|
||||
|
||||
The menu tree itself is available on the caller as
|
||||
`caller.ndb._menutree`. This makes it a convenient place to store
|
||||
`caller.ndb._evmenu`. This makes it a convenient place to store
|
||||
temporary state variables between nodes, since this NAttribute is
|
||||
deleted when the menu is exited.
|
||||
|
||||
|
|
@ -390,28 +390,28 @@ class CmdEvMenuNode(Command):
|
|||
caller = self.caller
|
||||
# we store Session on the menu since this can be hard to
|
||||
# get in multisession environemtns if caller is an Account.
|
||||
menu = caller.ndb._menutree
|
||||
menu = caller.ndb._evmenu
|
||||
if not menu:
|
||||
if _restore(caller):
|
||||
return
|
||||
orig_caller = caller
|
||||
caller = caller.account if hasattr(caller, "account") else None
|
||||
menu = caller.ndb._menutree if caller else None
|
||||
menu = caller.ndb._evmenu if caller else None
|
||||
if not menu:
|
||||
if caller and _restore(caller):
|
||||
return
|
||||
caller = self.session
|
||||
menu = caller.ndb._menutree
|
||||
menu = caller.ndb._evmenu
|
||||
if not menu:
|
||||
# can't restore from a session
|
||||
err = "Menu object not found as %s.ndb._menutree!" % orig_caller
|
||||
err = "Menu object not found as %s.ndb._evmenu!" % orig_caller
|
||||
orig_caller.msg(
|
||||
err
|
||||
) # don't give the session as a kwarg here, direct to original
|
||||
raise EvMenuError(err)
|
||||
# we must do this after the caller with the menu has been correctly identified since it
|
||||
# can be either Account, Object or Session (in the latter case this info will be superfluous).
|
||||
caller.ndb._menutree._session = self.session
|
||||
caller.ndb._evmenu._session = self.session
|
||||
# we have a menu, use it.
|
||||
menu.parse_input(self.raw_string)
|
||||
|
||||
|
|
@ -539,16 +539,16 @@ class EvMenu:
|
|||
`persistent` flag is deactivated.
|
||||
|
||||
Kwargs:
|
||||
any (any): All kwargs will become initialization variables on `caller.ndb._menutree`,
|
||||
any (any): All kwargs will become initialization variables on `caller.ndb._evmenu`,
|
||||
to be available at run.
|
||||
|
||||
Raises:
|
||||
EvMenuError: If the start/end node is not found in menu tree.
|
||||
|
||||
Notes:
|
||||
While running, the menu is stored on the caller as `caller.ndb._menutree`. Also
|
||||
While running, the menu is stored on the caller as `caller.ndb._evmenu`. Also
|
||||
the current Session (from the Command, so this is still valid in multisession
|
||||
environments) is available through `caller.ndb._menutree._session`. The `_menutree`
|
||||
environments) is available through `caller.ndb._evmenu._session`. The `_evmenu`
|
||||
property is a good one for storing intermediary data on between nodes since it
|
||||
will be automatically deleted when the menu closes.
|
||||
|
||||
|
|
@ -621,15 +621,18 @@ class EvMenu:
|
|||
for key, val in kwargs.items():
|
||||
setattr(self, key, val)
|
||||
|
||||
if self.caller.ndb._menutree:
|
||||
if self.caller.ndb._evmenu:
|
||||
# an evmenu already exists - we try to close it cleanly. Note that this will
|
||||
# not fire the previous menu's end node.
|
||||
try:
|
||||
self.caller.ndb._menutree.close_menu()
|
||||
self.caller.ndb._evmenu.close_menu()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# store ourself on the object
|
||||
self.caller.ndb._evmenu = self
|
||||
|
||||
# DEPRECATED - for backwards-compatibility
|
||||
self.caller.ndb._menutree = self
|
||||
|
||||
if persistent:
|
||||
|
|
@ -649,7 +652,7 @@ class EvMenu:
|
|||
caller.attributes.add("_menutree_saved", (self.__class__, (menudata,), calldict))
|
||||
caller.attributes.add("_menutree_saved_startnode", (startnode, startnode_input))
|
||||
except Exception as err:
|
||||
caller.msg(_ERROR_PERSISTENT_SAVING.format(error=err), session=self._session)
|
||||
self.msg(_ERROR_PERSISTENT_SAVING.format(error=err))
|
||||
logger.log_trace(_TRACE_PERSISTENT_SAVING)
|
||||
persistent = False
|
||||
|
||||
|
|
@ -761,7 +764,7 @@ class EvMenu:
|
|||
ret = callback(self.caller)
|
||||
except EvMenuError:
|
||||
errmsg = _ERR_GENERAL.format(nodename=callback)
|
||||
self.caller.msg(errmsg, self._session)
|
||||
self.msg(errmsg)
|
||||
logger.log_trace()
|
||||
raise
|
||||
|
||||
|
|
@ -786,7 +789,7 @@ class EvMenu:
|
|||
try:
|
||||
node = self._menutree[nodename]
|
||||
except KeyError:
|
||||
self.caller.msg(_ERR_NOT_IMPLEMENTED.format(nodename=nodename), session=self._session)
|
||||
self.msg(_ERR_NOT_IMPLEMENTED.format(nodename=nodename))
|
||||
raise EvMenuError
|
||||
try:
|
||||
kwargs["_current_nodename"] = nodename
|
||||
|
|
@ -796,11 +799,11 @@ class EvMenu:
|
|||
else:
|
||||
nodetext, options = ret, None
|
||||
except KeyError:
|
||||
self.caller.msg(_ERR_NOT_IMPLEMENTED.format(nodename=nodename), session=self._session)
|
||||
self.msg(_ERR_NOT_IMPLEMENTED.format(nodename=nodename))
|
||||
logger.log_trace()
|
||||
raise EvMenuError
|
||||
except Exception:
|
||||
self.caller.msg(_ERR_GENERAL.format(nodename=nodename), session=self._session)
|
||||
self.msg(_ERR_GENERAL.format(nodename=nodename))
|
||||
logger.log_trace()
|
||||
raise
|
||||
|
||||
|
|
@ -810,6 +813,23 @@ class EvMenu:
|
|||
|
||||
return nodetext, options
|
||||
|
||||
def msg(self, txt):
|
||||
"""
|
||||
This is a central point for sending return texts to the caller. It
|
||||
allows for a central point to add custom messaging when creating custom
|
||||
EvMenu overrides.
|
||||
|
||||
Args:
|
||||
txt (str): The text to send.
|
||||
|
||||
Notes:
|
||||
By default this will send to the same session provided to EvMenu
|
||||
(if `session` kwarg was provided to `EvMenu.__init__`). It will
|
||||
also send it with a `type=menu` for the benefit of OOB/webclient.
|
||||
|
||||
"""
|
||||
self.caller.msg(text=(txt, {"type": "menu"}), session=self._session)
|
||||
|
||||
def run_exec(self, nodename, raw_string, **kwargs):
|
||||
"""
|
||||
NOTE: This is deprecated. Use `goto` directly instead.
|
||||
|
|
@ -856,7 +876,7 @@ class EvMenu:
|
|||
ret, kwargs = ret[:2]
|
||||
except EvMenuError as err:
|
||||
errmsg = "Error in exec '%s' (input: '%s'): %s" % (nodename, raw_string.rstrip(), err)
|
||||
self.caller.msg("|r%s|n" % errmsg)
|
||||
self.msg("|r%s|n" % errmsg)
|
||||
logger.log_trace(errmsg)
|
||||
return
|
||||
|
||||
|
|
@ -1038,7 +1058,7 @@ class EvMenu:
|
|||
# avoid multiple calls from different sources
|
||||
self._quitting = True
|
||||
self.caller.cmdset.remove(EvMenuCmdSet)
|
||||
del self.caller.ndb._menutree
|
||||
del self.caller.ndb._evmenu
|
||||
if self._persistent:
|
||||
self.caller.attributes.remove("_menutree_saved")
|
||||
self.caller.attributes.remove("_menutree_saved_startnode")
|
||||
|
|
@ -1102,7 +1122,7 @@ class EvMenu:
|
|||
)
|
||||
+ "\n |y... END MENU DEBUG|n"
|
||||
)
|
||||
self.caller.msg(debugtxt)
|
||||
self.msg(debugtxt)
|
||||
|
||||
def parse_input(self, raw_string):
|
||||
"""
|
||||
|
|
@ -1137,17 +1157,17 @@ class EvMenu:
|
|||
goto, goto_kwargs, execfunc, exec_kwargs = self.default
|
||||
self.run_exec_then_goto(execfunc, goto, raw_string, exec_kwargs, goto_kwargs)
|
||||
else:
|
||||
self.caller.msg(_HELP_NO_OPTION_MATCH, session=self._session)
|
||||
self.msg(_HELP_NO_OPTION_MATCH)
|
||||
except EvMenuGotoAbortMessage as err:
|
||||
# custom interrupt from inside a goto callable - print the message and
|
||||
# stay on the current node.
|
||||
self.caller.msg(str(err), session=self._session)
|
||||
self.msg(str(err))
|
||||
|
||||
def display_nodetext(self):
|
||||
self.caller.msg(self.nodetext, session=self._session)
|
||||
self.msg(self.nodetext)
|
||||
|
||||
def display_helptext(self):
|
||||
self.caller.msg(self.helptext, session=self._session)
|
||||
self.msg(self.helptext)
|
||||
|
||||
# formatters - override in a child class
|
||||
|
||||
|
|
@ -1732,7 +1752,6 @@ def parse_menu_template(caller, menu_template, goto_callables=None):
|
|||
# if we have a pattern, build the arguments for _default later
|
||||
pattern = main_key[len(_OPTION_INPUT_MARKER):].strip()
|
||||
inputparsemap[pattern] = goto
|
||||
print(f"main_key {main_key} {pattern} {goto}")
|
||||
else:
|
||||
# a regular goto string/callable target
|
||||
option = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue