Merge branch 'develop' into global-scripts-raise
This commit is contained in:
commit
d1981bd15b
62 changed files with 1995 additions and 818 deletions
|
|
@ -5,6 +5,7 @@ Test the funcparser module.
|
|||
"""
|
||||
|
||||
import time
|
||||
import unittest
|
||||
from ast import literal_eval
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
|
|
@ -77,6 +78,10 @@ def _lsum_callable(*args, **kwargs):
|
|||
return ""
|
||||
|
||||
|
||||
def _raises_callable(*args, **kwargs):
|
||||
raise RuntimeError("Test exception raised by test callable")
|
||||
|
||||
|
||||
_test_callables = {
|
||||
"foo": _test_callable,
|
||||
"bar": _test_callable,
|
||||
|
|
@ -89,6 +94,7 @@ _test_callables = {
|
|||
"add": _add_callable,
|
||||
"lit": _lit_callable,
|
||||
"sum": _lsum_callable,
|
||||
"raise": _raises_callable,
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -102,6 +108,22 @@ class TestFuncParser(TestCase):
|
|||
|
||||
self.parser = funcparser.FuncParser(_test_callables)
|
||||
|
||||
def test_constructor_wrong_args(self):
|
||||
# Given list argument doesn't contain modules or paths.
|
||||
with self.assertRaises(AttributeError):
|
||||
parser = funcparser.FuncParser(["foo", _test_callable])
|
||||
|
||||
def test_constructor_ignore_non_callables(self):
|
||||
# Ignores callables that aren't actual functions.
|
||||
parser = funcparser.FuncParser({"foo": 1, "bar": "baz"})
|
||||
|
||||
@patch("evennia.utils.funcparser.variable_from_module")
|
||||
def test_constructor_raises(self, patched_variable_from_module):
|
||||
# Patched variable from module returns FUNCPARSER_CALLABLES that isn't dict.
|
||||
patched_variable_from_module.return_value = ["foo"]
|
||||
with self.assertRaises(funcparser.ParsingError):
|
||||
parser = funcparser.FuncParser("foo.module")
|
||||
|
||||
@parameterized.expand(
|
||||
[
|
||||
("Test normal string", "Test normal string"),
|
||||
|
|
@ -216,13 +238,49 @@ class TestFuncParser(TestCase):
|
|||
# print(f"time: {(t1-t0)*1000} ms")
|
||||
self.assertEqual(expected, ret)
|
||||
|
||||
def test_parse_raise(self):
|
||||
@parameterized.expand(
|
||||
(
|
||||
"Test malformed This is $dummy(a, b) and $bar(",
|
||||
"Test $funcNotFound()",
|
||||
)
|
||||
)
|
||||
def test_parse_raise_unparseable(self, unparseable):
|
||||
"""
|
||||
Make sure error is raised if told to do so.
|
||||
|
||||
"""
|
||||
string = "Test malformed This is $dummy(a, b) and $bar("
|
||||
with self.assertRaises(funcparser.ParsingError):
|
||||
self.parser.parse(unparseable, raise_errors=True)
|
||||
|
||||
@patch("evennia.utils.funcparser._MAX_NESTING", 2)
|
||||
def test_parse_max_nesting(self):
|
||||
"""
|
||||
Make sure it is an error if the max nesting value is reached.
|
||||
|
||||
TODO: Does this make sense? When it sees the first function, len(callstack)
|
||||
is 0. It doesn't raise until the stack length is greater than the
|
||||
_MAX_NESTING value, which means you can nest 4 values with a value of
|
||||
2, as demonstrated by this test.
|
||||
"""
|
||||
string = "$add(1, $add(1, $add(1, $toint(42))))"
|
||||
ret = self.parser.parse(string)
|
||||
|
||||
# TODO: Does this return value actually make sense?
|
||||
# It removed the spaces from the calls.
|
||||
self.assertEqual("$add(1,$add(1,$add(1,$toint(42))))", ret)
|
||||
|
||||
with self.assertRaises(funcparser.ParsingError):
|
||||
self.parser.parse(string, raise_errors=True)
|
||||
|
||||
def test_parse_underlying_exception(self):
|
||||
string = "test $add(1, 1) $raise()"
|
||||
ret = self.parser.parse(string)
|
||||
|
||||
# TODO: Does this return value actually make sense?
|
||||
# It completed the first function call.
|
||||
self.assertEqual("test 2 $raise()", ret)
|
||||
|
||||
with self.assertRaises(RuntimeError):
|
||||
self.parser.parse(string, raise_errors=True)
|
||||
|
||||
def test_parse_strip(self):
|
||||
|
|
@ -234,6 +292,12 @@ class TestFuncParser(TestCase):
|
|||
ret = self.parser.parse(string, strip=True)
|
||||
self.assertEqual("Test and things", ret)
|
||||
|
||||
@unittest.skip("broken due to https://github.com/evennia/evennia/issues/2927")
|
||||
def test_parse_whitespace_preserved(self):
|
||||
string = "The answer is $add(1, x)"
|
||||
ret = self.parser.parse(string)
|
||||
self.assertEqual("The answer is $add(1, x)", ret)
|
||||
|
||||
def test_parse_escape(self):
|
||||
"""
|
||||
Test the parser's escape functionality.
|
||||
|
|
@ -368,8 +432,7 @@ class TestDefaultCallables(TestCase):
|
|||
)
|
||||
def test_conjugate(self, string, expected_you, expected_them):
|
||||
"""
|
||||
Test callables with various input strings
|
||||
|
||||
Test the $conj(), $you() and $pron callables with various input strings.
|
||||
"""
|
||||
mapping = {"char1": self.obj1, "char2": self.obj2}
|
||||
ret = self.parser.parse(
|
||||
|
|
@ -381,6 +444,46 @@ class TestDefaultCallables(TestCase):
|
|||
)
|
||||
self.assertEqual(expected_them, ret)
|
||||
|
||||
def test_conjugate_missing_args(self):
|
||||
string = "You $conj(smile)"
|
||||
with self.assertRaises(funcparser.ParsingError):
|
||||
self.parser.parse(string, raise_errors=True)
|
||||
|
||||
@parameterized.expand(
|
||||
[
|
||||
("male", "Char1 smiles at himself"),
|
||||
("female", "Char1 smiles at herself"),
|
||||
("neutral", "Char1 smiles at itself"),
|
||||
("plural", "Char1 smiles at itself"),
|
||||
]
|
||||
)
|
||||
def test_pronoun_gender(self, gender, expected):
|
||||
string = "Char1 smiles at $pron(yourself)"
|
||||
|
||||
self.obj1.gender = gender
|
||||
ret = self.parser.parse(string, caller=self.obj1, raise_errors=True)
|
||||
self.assertEqual(expected, ret)
|
||||
|
||||
self.obj1.gender = lambda: gender
|
||||
ret = self.parser.parse(string, caller=self.obj1, raise_errors=True)
|
||||
self.assertEqual(expected, ret)
|
||||
|
||||
def test_pronoun_viewpoint(self):
|
||||
string = "Char1 smiles at $pron(I)"
|
||||
|
||||
ret = self.parser.parse(string, caller=self.obj1, viewpoint="op", raise_errors=True)
|
||||
self.assertEqual("Char1 smiles at it", ret)
|
||||
|
||||
def test_pronoun_capitalize(self):
|
||||
string = "Char1 smiles at $pron(I)"
|
||||
|
||||
ret = self.parser.parse(string, caller=self.obj1, capitalize=True, raise_errors=True)
|
||||
self.assertEqual("Char1 smiles at It", ret)
|
||||
|
||||
string = "Char1 smiles at $Pron(I)"
|
||||
ret = self.parser.parse(string, caller=self.obj1, capitalize=True, raise_errors=True)
|
||||
self.assertEqual("Char1 smiles at It", ret)
|
||||
|
||||
@parameterized.expand(
|
||||
[
|
||||
("Test $pad(Hello, 20, c, -) there", "Test -------Hello-------- there"),
|
||||
|
|
@ -396,6 +499,7 @@ class TestDefaultCallables(TestCase):
|
|||
("Some $mult(3, 2) things", "Some 6 things"),
|
||||
("Some $div(6, 2) things", "Some 3.0 things"),
|
||||
("Some $toint(6) things", "Some 6 things"),
|
||||
("Some $toint(3 + 3) things", "Some 6 things"),
|
||||
("Some $ljust(Hello, 30)", "Some Hello "),
|
||||
("Some $rjust(Hello, 30)", "Some Hello"),
|
||||
("Some $rjust(Hello, width=30)", "Some Hello"),
|
||||
|
|
@ -415,6 +519,33 @@ class TestDefaultCallables(TestCase):
|
|||
("There is $an(thing) here", "There is a thing here"),
|
||||
("Some $eval(\"'-'*20\")Hello", "Some --------------------Hello"),
|
||||
('$crop("spider\'s silk", 5)', "spide"),
|
||||
("$an(apple)", "an apple"),
|
||||
# These two are broken because of https://github.com/evennia/evennia/issues/2912
|
||||
# ("$round(2.9) apples", "3.0 apples"),
|
||||
# ("$round(2.967, 1) apples", "3.0 apples"),
|
||||
# Degenerate cases
|
||||
("$int2str() apples", " apples"),
|
||||
("$int2str(x) apples", "x apples"),
|
||||
("$int2str(1 + 1) apples", "1 + 1 apples"),
|
||||
("$int2str(13) apples", "13 apples"),
|
||||
("$toint([1, 2, 3]) apples", "[1, 2, 3] apples"),
|
||||
("$an() foo bar", " foo bar"),
|
||||
("$add(1) apple", " apple"),
|
||||
("$add(1, [1, 2]) apples", " apples"),
|
||||
("$round() apples", " apples"),
|
||||
("$choice() apple", " apple"),
|
||||
("A $pad() apple", "A apple"),
|
||||
("A $pad(tasty, 13, x, -) apple", "A ----tasty---- apple"),
|
||||
("A $crop() apple", "A apple"),
|
||||
("A $space() apple", "A apple"),
|
||||
("A $justify() apple", "A apple"),
|
||||
("A $clr() apple", "A apple"),
|
||||
("A $clr(red) apple", "A red apple"),
|
||||
("10 $pluralize()", "10 "),
|
||||
("10 $pluralize(apple, 10)", "10 apples"),
|
||||
("1 $pluralize(apple)", "1 apple"),
|
||||
("You $conj()", "You "),
|
||||
("$pron() smiles", " smiles"),
|
||||
]
|
||||
)
|
||||
def test_other_callables(self, string, expected):
|
||||
|
|
@ -426,6 +557,9 @@ class TestDefaultCallables(TestCase):
|
|||
self.assertEqual(expected, ret)
|
||||
|
||||
def test_random(self):
|
||||
"""
|
||||
Test random callable, with ranges of expected values.
|
||||
"""
|
||||
string = "$random(1,10)"
|
||||
for i in range(100):
|
||||
ret = self.parser.parse_to_any(string, raise_errors=True)
|
||||
|
|
@ -436,12 +570,52 @@ class TestDefaultCallables(TestCase):
|
|||
ret = self.parser.parse_to_any(string, raise_errors=True)
|
||||
self.assertTrue(0 <= ret <= 1)
|
||||
|
||||
string = "$random(2)"
|
||||
for i in range(100):
|
||||
ret = self.parser.parse_to_any(string, raise_errors=True)
|
||||
self.assertTrue(0 <= ret <= 2)
|
||||
|
||||
string = "$random(1.0, 3.0)"
|
||||
for i in range(100):
|
||||
ret = self.parser.parse_to_any(string, raise_errors=True)
|
||||
self.assertTrue(isinstance(ret, float))
|
||||
self.assertTrue(1.0 <= ret <= 3.0)
|
||||
|
||||
string = "$random([1,2]) apples"
|
||||
ret = self.parser.parse_to_any(string)
|
||||
self.assertEqual(" apples", ret)
|
||||
with self.assertRaises(TypeError):
|
||||
ret = self.parser.parse_to_any(string, raise_errors=True)
|
||||
|
||||
# @unittest.skip("underlying function seems broken")
|
||||
def test_choice(self):
|
||||
"""
|
||||
Test choice callable, where output could be either choice.
|
||||
"""
|
||||
string = "$choice(red, green) apple"
|
||||
ret = self.parser.parse(string)
|
||||
self.assertIn(ret, ("red apple", "green apple"))
|
||||
|
||||
string = "$choice([red, green]) apple"
|
||||
ret = self.parser.parse(string)
|
||||
self.assertIn(ret, ("red apple", "green apple"))
|
||||
|
||||
string = "$choice(['red', 'green']) apple"
|
||||
ret = self.parser.parse(string)
|
||||
self.assertIn(ret, ("red apple", "green apple"))
|
||||
|
||||
string = "$choice([1, 2])"
|
||||
ret = self.parser.parse(string)
|
||||
self.assertIn(ret, ("1", "2"))
|
||||
ret = self.parser.parse_to_any(string)
|
||||
self.assertIn(ret, (1, 2))
|
||||
|
||||
string = "$choice(1, 2)"
|
||||
ret = self.parser.parse(string)
|
||||
self.assertIn(ret, ("1", "2"))
|
||||
ret = self.parser.parse_to_any(string)
|
||||
self.assertIn(ret, (1, 2))
|
||||
|
||||
def test_randint(self):
|
||||
string = "$randint(1.0, 3.0)"
|
||||
ret = self.parser.parse_to_any(string, raise_errors=True)
|
||||
|
|
@ -528,6 +702,7 @@ class TestCallableSearch(test_resources.BaseEvenniaTest):
|
|||
"""
|
||||
string = "$search(TestAccount, type=account)"
|
||||
expected = self.account
|
||||
self.account.locks.add("control:id(%s)" % self.char1.dbref)
|
||||
|
||||
ret = self.parser.parse(string, caller=self.char1, return_str=False, raise_errors=True)
|
||||
self.assertEqual(expected, ret)
|
||||
|
|
@ -539,6 +714,7 @@ class TestCallableSearch(test_resources.BaseEvenniaTest):
|
|||
"""
|
||||
string = "$search(Script, type=script)"
|
||||
expected = self.script
|
||||
self.script.locks.add("control:id(%s)" % self.char1.dbref)
|
||||
|
||||
ret = self.parser.parse(string, caller=self.char1, return_str=False, raise_errors=True)
|
||||
self.assertEqual(expected, ret)
|
||||
|
|
@ -553,3 +729,86 @@ class TestCallableSearch(test_resources.BaseEvenniaTest):
|
|||
|
||||
ret = self.parser.parse(string, caller=self.char1, return_str=False, raise_errors=True)
|
||||
self.assertEqual(expected, ret)
|
||||
|
||||
def test_search_tag(self):
|
||||
"""
|
||||
Test searching for a tag
|
||||
"""
|
||||
self.char1.tags.add("foo")
|
||||
|
||||
string = "This is $search(foo, type=tag)"
|
||||
expected = "This is %s" % str(self.char1)
|
||||
|
||||
ret = self.parser.parse(string, caller=self.char1, return_str=False, raise_errors=True)
|
||||
self.assertEqual(expected, ret)
|
||||
|
||||
def test_search_not_found(self):
|
||||
string = "$search(foo)"
|
||||
with self.assertRaises(funcparser.ParsingError):
|
||||
self.parser.parse(string, caller=self.char1, return_str=False, raise_errors=True)
|
||||
|
||||
ret = self.parser.parse(string, caller=self.char1, return_str=False, raise_errors=False)
|
||||
self.assertEqual("$search(foo)", ret)
|
||||
|
||||
ret = self.parser.parse_to_any(
|
||||
string, caller=self.char1, return_list=True, raise_errors=False
|
||||
)
|
||||
self.assertEqual([], ret)
|
||||
|
||||
def test_search_multiple_results_no_list(self):
|
||||
"""
|
||||
Test exception when search returns multiple results but list is not requested
|
||||
"""
|
||||
string = "$search(BaseObject)"
|
||||
with self.assertRaises(funcparser.ParsingError):
|
||||
self.parser.parse(string, caller=self.char1, return_str=False, raise_errors=True)
|
||||
|
||||
def test_search_no_access(self):
|
||||
string = "Go to $search(Room)"
|
||||
with self.assertRaises(funcparser.ParsingError):
|
||||
self.parser.parse(string, caller=self.char2, return_list=True, raise_errors=True)
|
||||
|
||||
def test_search_no_caller(self):
|
||||
string = "$search(Char)"
|
||||
with self.assertRaises(funcparser.ParsingError):
|
||||
self.parser.parse(string, caller=None, raise_errors=True)
|
||||
|
||||
def test_search_no_args(self):
|
||||
string = "$search()"
|
||||
ret = self.parser.parse(string, caller=self.char1, return_list=False, raise_errors=True)
|
||||
self.assertEqual("None", ret)
|
||||
|
||||
ret = self.parser.parse(string, caller=self.char1, return_list=True, raise_errors=True)
|
||||
self.assertEqual("[]", ret)
|
||||
|
||||
def test_search_nested__issue2902(self):
|
||||
"""
|
||||
Search for objects by-tag, check that the result is a valid object
|
||||
|
||||
"""
|
||||
# we
|
||||
parser = funcparser.FuncParser(
|
||||
{**funcparser.SEARCHING_CALLABLES, **funcparser.FUNCPARSER_CALLABLES}
|
||||
)
|
||||
|
||||
# set up search targets
|
||||
self.obj1.tags.add("beach", category="zone")
|
||||
self.obj2.tags.add("beach", category="zone")
|
||||
|
||||
# first a plain search
|
||||
string = "$objlist(beach,category=zone,type=tag)"
|
||||
ret = parser.parse_to_any(string, caller=self.char1, raise_errors=True)
|
||||
|
||||
self.assertEqual(ret, [self.obj1, self.obj2])
|
||||
|
||||
# get random result from the possible matches
|
||||
string = "$choice($objlist(beach,category=zone,type=tag))"
|
||||
ret = parser.parse_to_any(string, caller=self.char1, raise_errors=True)
|
||||
|
||||
self.assertIn(ret, [self.obj1, self.obj2])
|
||||
|
||||
# test wrapping in $obj(), should just pass object through
|
||||
string = "$obj($choice($objlist(beach,category=zone,type=tag)))"
|
||||
ret = parser.parse_to_any(string, caller=self.char1, raise_errors=True)
|
||||
|
||||
self.assertIn(ret, [self.obj1, self.obj2])
|
||||
|
|
|
|||
|
|
@ -1,6 +1,13 @@
|
|||
from evennia import DefaultObject, DefaultRoom
|
||||
from evennia.objects.models import ObjectDB
|
||||
from evennia.scripts.scripts import DefaultScript
|
||||
from evennia.utils.search import (
|
||||
search_script,
|
||||
search_script_attribute,
|
||||
search_script_tag,
|
||||
search_typeclass,
|
||||
)
|
||||
from evennia.utils.test_resources import EvenniaTest
|
||||
from evennia.utils.search import search_script_attribute, search_script_tag, search_script
|
||||
|
||||
|
||||
class TestSearch(EvenniaTest):
|
||||
|
|
@ -61,3 +68,15 @@ class TestSearch(EvenniaTest):
|
|||
script, errors = DefaultScript.create("a-script")
|
||||
found = search_script("wrong_key")
|
||||
self.assertEqual(len(found), 0, errors)
|
||||
|
||||
def test_search_typeclass(self):
|
||||
"""Check that an object can be found by typeclass"""
|
||||
DefaultObject.create("test_obj")
|
||||
found = search_typeclass("evennia.objects.objects.DefaultObject")
|
||||
self.assertEqual(len(found), 1)
|
||||
|
||||
def test_search_wrong_typeclass(self):
|
||||
"""Check that an object cannot be found by wrong typeclass"""
|
||||
DefaultObject.create("test_obj_2")
|
||||
with self.assertRaises(ImportError):
|
||||
search_typeclass("not.a.typeclass")
|
||||
|
|
|
|||
|
|
@ -66,8 +66,12 @@ class TestListToString(TestCase):
|
|||
[1,2,3] -> '1, 2, 3'
|
||||
with sep==';' and endsep==';':
|
||||
[1,2,3] -> '1; 2; 3'
|
||||
with sep=='or':
|
||||
[1,2,3] -> '1 or 2, and 3'
|
||||
with endsep=='and':
|
||||
[1,2,3] -> '1, 2 and 3'
|
||||
with endsep=='; and':
|
||||
[1,2,3] -> '1, 2; and 3'
|
||||
with endsep=='':
|
||||
[1,2,3] -> '1, 2 3'
|
||||
with addquote and endsep="and"
|
||||
|
|
@ -80,6 +84,8 @@ class TestListToString(TestCase):
|
|||
self.assertEqual("1, 2 and 3", utils.list_to_string([1, 2, 3], endsep="and"))
|
||||
self.assertEqual("1, 2 3", utils.list_to_string([1, 2, 3], endsep=""))
|
||||
self.assertEqual("1; 2; 3", utils.list_to_string([1, 2, 3], sep=";", endsep=";"))
|
||||
self.assertEqual("1 or 2, and 3", utils.list_to_string([1, 2, 3], sep="or"))
|
||||
self.assertEqual("1, 2; and 3", utils.list_to_string([1, 2, 3], endsep="; and"))
|
||||
self.assertEqual(
|
||||
'"1", "2", "3"', utils.list_to_string([1, 2, 3], endsep=",", addquote=True)
|
||||
)
|
||||
|
|
@ -696,3 +702,36 @@ class TestDelay(BaseEvenniaTest):
|
|||
timedelay
|
||||
) # Clock must advance to trigger, even if past timedelay
|
||||
self.assertEqual(self.char1.ndb.dummy_var, "dummy_func ran")
|
||||
|
||||
|
||||
class TestIntConversions(TestCase):
|
||||
def test_int2str(self):
|
||||
self.assertEqual("three", utils.int2str(3))
|
||||
# special adjective conversion
|
||||
self.assertEqual("3rd", utils.int2str(3, adjective=True))
|
||||
# generic adjective conversion
|
||||
self.assertEqual("5th", utils.int2str(5, adjective=True))
|
||||
# No mapping return int as str
|
||||
self.assertEqual("15", utils.int2str(15))
|
||||
|
||||
def test_str2int(self):
|
||||
# simple conversions
|
||||
self.assertEqual(5, utils.str2int("5"))
|
||||
|
||||
# basic mapped numbers
|
||||
self.assertEqual(3, utils.str2int("three"))
|
||||
self.assertEqual(20, utils.str2int("twenty"))
|
||||
|
||||
# multi-place numbers
|
||||
self.assertEqual(2345, utils.str2int("two thousand, three hundred and forty-five"))
|
||||
|
||||
# ordinal numbers
|
||||
self.assertEqual(1, utils.str2int("1st"))
|
||||
self.assertEqual(1, utils.str2int("first"))
|
||||
self.assertEqual(4, utils.str2int("fourth"))
|
||||
# ordinal sound-change conversions
|
||||
self.assertEqual(5, utils.str2int("fifth"))
|
||||
self.assertEqual(20, utils.str2int("twentieth"))
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
utils.str2int("not a number")
|
||||
Loading…
Add table
Add a link
Reference in a new issue