mirror of
https://github.com/evennia/evennia.git
synced 2026-04-05 07:27:17 +02:00
342 lines
12 KiB
Python
342 lines
12 KiB
Python
"""
|
|
Unit tests for the EvMenu system
|
|
|
|
This sets up a testing parent for testing EvMenu trees. It is configured by subclassing the
|
|
`TestEvMenu` class from this module and setting the class variables to point to the menu that should
|
|
be tested and how it should be called.
|
|
|
|
Without adding any further test methods, the tester will process all nodes of the menu, depth first,
|
|
by stepping through all options for every node. Optionally, it can check that all nodes are visited.
|
|
It will create a hierarchical list of node names that describes the tree structure. This can then be
|
|
compared against a template to make sure the menu structure is sound. Easiest way to use this is to
|
|
run the test once to see how the structure looks.
|
|
|
|
The system also allows for testing the returns of each node as part of the parsing.
|
|
|
|
To help debug the menu, turn on `debug_output`, which will print the traversal process in detail.
|
|
|
|
"""
|
|
|
|
import copy
|
|
from anything import Anything
|
|
from django.test import TestCase
|
|
from evennia.utils.test_resources import BaseEvenniaTest
|
|
from evennia.utils import evmenu
|
|
from evennia.utils import ansi
|
|
from mock import MagicMock
|
|
|
|
|
|
class TestEvMenu(TestCase):
|
|
"Run the EvMenu testing."
|
|
menutree = {} # can also be the path to the menu tree
|
|
startnode = "start"
|
|
cmdset_mergetype = "Replace"
|
|
cmdset_priority = 1
|
|
auto_quit = True
|
|
auto_look = True
|
|
auto_help = True
|
|
cmd_on_exit = "look"
|
|
persistent = False
|
|
startnode_input = ""
|
|
kwargs = {}
|
|
|
|
# if all nodes must be visited for the test to pass. This is not on
|
|
# by default since there may be exec-nodes that are made to not be
|
|
# visited.
|
|
expect_all_nodes = False
|
|
|
|
# this is compared against the full tree structure generated
|
|
expected_tree = []
|
|
# this allows for verifying that a given node returns a given text. The
|
|
# text is compared with .startswith, so the entire text need not be matched.
|
|
expected_node_texts = {}
|
|
# just check the number of options from each node
|
|
expected_node_options_count = {}
|
|
# check the actual options
|
|
expected_node_options = {}
|
|
|
|
# set this to print the traversal as it happens (debugging)
|
|
debug_output = False
|
|
|
|
def _debug_output(self, indent, msg):
|
|
if self.debug_output:
|
|
print(" " * indent + ansi.strip_ansi(msg))
|
|
|
|
def _test_menutree(self, menu):
|
|
"""
|
|
This is a automatic tester of the menu tree by recursively progressing through the
|
|
structure.
|
|
"""
|
|
|
|
def _depth_first(menu, tree, visited, indent):
|
|
|
|
# we are in a given node here
|
|
nodename = menu.nodename
|
|
options = menu.test_options
|
|
if isinstance(options, dict):
|
|
options = (options,)
|
|
|
|
# run validation tests for this node
|
|
compare_text = self.expected_node_texts.get(nodename, None)
|
|
if compare_text is not None:
|
|
compare_text = ansi.strip_ansi(compare_text.strip())
|
|
node_text = menu.test_nodetext
|
|
self.assertIsNotNone(
|
|
bool(node_text),
|
|
"node: {}: node-text is None, which was not expected.".format(nodename),
|
|
)
|
|
if isinstance(node_text, tuple):
|
|
node_text, helptext = node_text
|
|
node_text = ansi.strip_ansi(node_text.strip())
|
|
self.assertTrue(
|
|
node_text.startswith(compare_text),
|
|
"\nnode \"{}':\nOutput:\n{}\n\nExpected (startswith):\n{}".format(
|
|
nodename, node_text, compare_text
|
|
),
|
|
)
|
|
compare_options_count = self.expected_node_options_count.get(nodename, None)
|
|
if compare_options_count is not None:
|
|
self.assertEqual(
|
|
len(options),
|
|
compare_options_count,
|
|
"Not the right number of options returned from node {}.".format(nodename),
|
|
)
|
|
compare_options = self.expected_node_options.get(nodename, None)
|
|
if compare_options:
|
|
self.assertEqual(
|
|
options,
|
|
compare_options,
|
|
"Options returned from node {} does not match.".format(nodename),
|
|
)
|
|
|
|
self._debug_output(indent, "*{}".format(nodename))
|
|
subtree = []
|
|
|
|
if not options:
|
|
# an end node
|
|
if nodename not in visited:
|
|
visited.append(nodename)
|
|
subtree = nodename
|
|
else:
|
|
for inum, optdict in enumerate(options):
|
|
|
|
key, desc, execute, goto = (
|
|
optdict.get("key", ""),
|
|
optdict.get("desc", None),
|
|
optdict.get("exec", None),
|
|
optdict.get("goto", None),
|
|
)
|
|
|
|
# prepare the key to pass to the menu
|
|
if isinstance(key, (tuple, list)) and len(key) > 1:
|
|
key = key[0]
|
|
if key == "_default":
|
|
key = "test raw input"
|
|
if not key:
|
|
key = str(inum + 1)
|
|
|
|
backup_menu = copy.copy(menu)
|
|
|
|
# step the menu
|
|
menu.parse_input(key)
|
|
|
|
# from here on we are likely in a different node
|
|
nodename = menu.nodename
|
|
|
|
if menu.close_menu.called:
|
|
# this was an end node
|
|
self._debug_output(indent, " .. menu exited! Back to previous node.")
|
|
menu = backup_menu
|
|
menu.close_menu = MagicMock()
|
|
visited.append(nodename)
|
|
subtree.append(nodename)
|
|
elif nodename not in visited:
|
|
visited.append(nodename)
|
|
subtree.append(nodename)
|
|
_depth_first(menu, subtree, visited, indent + 2)
|
|
# self._debug_output(indent, " -> arrived at {}".format(nodename))
|
|
else:
|
|
subtree.append(nodename)
|
|
# self._debug_output( indent, " -> arrived at {} (circular call)".format(nodename))
|
|
self._debug_output(indent, "-- {} ({}) -> {}".format(key, desc, goto))
|
|
|
|
if subtree:
|
|
tree.append(subtree)
|
|
|
|
# the start node has already fired at this point
|
|
visited_nodes = [menu.nodename]
|
|
traversal_tree = [menu.nodename]
|
|
_depth_first(menu, traversal_tree, visited_nodes, 1)
|
|
|
|
if self.expect_all_nodes:
|
|
self.assertGreaterEqual(len(menu._menutree), len(visited_nodes))
|
|
self.assertEqual(traversal_tree, self.expected_tree)
|
|
|
|
def setUp(self):
|
|
self.menu = None
|
|
if self.menutree:
|
|
self.caller = MagicMock()
|
|
self.caller.key = "Test"
|
|
self.caller2 = MagicMock()
|
|
self.caller2.key = "Test"
|
|
self.caller.msg = MagicMock()
|
|
self.caller2.msg = MagicMock()
|
|
self.session = MagicMock()
|
|
self.session.protocol_flags = {}
|
|
self.session2 = MagicMock()
|
|
self.session2.protocol_flags = {}
|
|
self.caller.session = self.session
|
|
self.caller2.session = self.session2
|
|
|
|
self.menu = evmenu.EvMenu(
|
|
self.caller,
|
|
self.menutree,
|
|
startnode=self.startnode,
|
|
cmdset_mergetype=self.cmdset_mergetype,
|
|
cmdset_priority=self.cmdset_priority,
|
|
auto_quit=self.auto_quit,
|
|
auto_look=self.auto_look,
|
|
auto_help=self.auto_help,
|
|
cmd_on_exit=self.cmd_on_exit,
|
|
persistent=False,
|
|
startnode_input=self.startnode_input,
|
|
session=self.session,
|
|
**self.kwargs,
|
|
)
|
|
# persistent version
|
|
self.pmenu = evmenu.EvMenu(
|
|
self.caller2,
|
|
self.menutree,
|
|
startnode=self.startnode,
|
|
cmdset_mergetype=self.cmdset_mergetype,
|
|
cmdset_priority=self.cmdset_priority,
|
|
auto_quit=self.auto_quit,
|
|
auto_look=self.auto_look,
|
|
auto_help=self.auto_help,
|
|
cmd_on_exit=self.cmd_on_exit,
|
|
persistent=True,
|
|
startnode_input=self.startnode_input,
|
|
session=self.session2,
|
|
**self.kwargs,
|
|
)
|
|
|
|
self.menu.close_menu = MagicMock()
|
|
self.pmenu.close_menu = MagicMock()
|
|
|
|
def test_menu_structure(self):
|
|
if self.menu:
|
|
self._test_menutree(self.menu)
|
|
self._test_menutree(self.pmenu)
|
|
|
|
|
|
class TestEvMenuExample(TestEvMenu):
|
|
|
|
menutree = "evennia.utils.tests.data.evmenu_example"
|
|
startnode = "test_start_node"
|
|
kwargs = {"testval": "val", "testval2": "val2"}
|
|
debug_output = False
|
|
|
|
expected_node_texts = {"test_view_node": "Your name is"}
|
|
|
|
expected_tree = [
|
|
"test_start_node",
|
|
[
|
|
"test_set_node",
|
|
["test_start_node"],
|
|
"test_look_node",
|
|
["test_start_node"],
|
|
"test_view_node",
|
|
["test_start_node"],
|
|
"test_dynamic_node",
|
|
[
|
|
"test_dynamic_node",
|
|
"test_dynamic_node",
|
|
"test_dynamic_node",
|
|
"test_dynamic_node",
|
|
"test_start_node",
|
|
],
|
|
"test_end_node",
|
|
"test_displayinput_node",
|
|
["test_start_node"],
|
|
],
|
|
]
|
|
|
|
def test_kwargsave(self):
|
|
self.assertTrue(hasattr(self.menu, "testval"))
|
|
self.assertTrue(hasattr(self.menu, "testval2"))
|
|
|
|
|
|
def _callnode1(caller, raw_string, **kwargs):
|
|
return "node1"
|
|
|
|
|
|
def _callnode2(caller, raw_string, **kwargs):
|
|
return "node2"
|
|
|
|
|
|
class TestMenuTemplateParse(BaseEvenniaTest):
|
|
"""Test menu templating helpers"""
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
|
|
self.menu_template = """
|
|
## node start
|
|
|
|
Neque ea alias perferendis molestiae eligendi. Debitis exercitationem
|
|
exercitationem quas blanditiis quisquam officia ut. Fugit aut fugit enim quia
|
|
non. Earum et excepturi animi ex esse accusantium et. Id adipisci eos enim
|
|
ratione.
|
|
|
|
## options
|
|
|
|
1: first option -> node1
|
|
2: second option -> node2
|
|
next: node1
|
|
|
|
## node node1
|
|
|
|
Node 1
|
|
|
|
## options
|
|
|
|
fwd: node2
|
|
call1: callnode1()
|
|
call2: callnode2(foo=bar, bar=22, goo="another test")
|
|
>: start
|
|
|
|
## node node2
|
|
|
|
Text of node 2
|
|
|
|
## options
|
|
|
|
> foo*: node1
|
|
> [0-9]+?: node2
|
|
> back: start
|
|
|
|
"""
|
|
self.goto_callables = {"callnode1": _callnode1, "callnode2": _callnode2}
|
|
|
|
def test_parse_menu_template(self):
|
|
"""EvMenu template testing"""
|
|
|
|
menutree = evmenu.parse_menu_template(self.char1, self.menu_template,
|
|
self.goto_callables)
|
|
self.assertEqual(menutree, {"start": Anything, "node1": Anything, "node2": Anything})
|
|
|
|
def test_template2menu(self):
|
|
evmenu.template2menu(self.char1, self.menu_template, self.goto_callables)
|
|
|
|
def test_parse_menu_fail(self):
|
|
template = """
|
|
## NODE
|
|
|
|
Text
|
|
|
|
## OPTIONS
|
|
|
|
next: callnode2(invalid)
|
|
"""
|
|
with self.assertRaises(RuntimeError):
|
|
evmenu.parse_menu_template(self.char1, template, self.goto_callables)
|