From b6649bcef8a87c470e033bc34dfde74a65866b6d Mon Sep 17 00:00:00 2001 From: Scyfris Talivinsky Date: Fri, 6 Oct 2017 01:34:10 -0700 Subject: [PATCH 1/2] Add a @spawn unit test Tests the @spawn command and its variations. The test is designed to aslo check the properties of the spawned objects for some common/obvious settings. Test also adds a new prototypes file to be used with the Evennia unit tester for any tests that require named prototypes. The spawner test requires testing named prototypes, so it is added in this change. --- evennia/commands/default/tests.py | 69 +++++++++++++++++++++++++++++++ evennia/utils/test_prototypes.py | 20 +++++++++ evennia/utils/test_resources.py | 2 + 3 files changed, 91 insertions(+) create mode 100644 evennia/utils/test_prototypes.py diff --git a/evennia/commands/default/tests.py b/evennia/commands/default/tests.py index 20eb3bacae..ffb63ef723 100644 --- a/evennia/commands/default/tests.py +++ b/evennia/commands/default/tests.py @@ -24,6 +24,8 @@ from evennia.commands.default import help, general, system, admin, account, buil from evennia.commands.command import Command, InterruptCommand from evennia.utils import ansi, utils from evennia.server.sessionhandler import SESSIONS +from evennia import search_object +from evennia import DefaultObject, DefaultCharacter # set up signal here since we are not starting the server @@ -296,6 +298,73 @@ class TestBuilding(CommandTest): def test_teleport(self): self.call(building.CmdTeleport(), "Room2", "Room2(#2)\n|Teleported to Room2.") + def test_spawn(self): + def getObject(commandTest, objKeyStr): + # A helper function to get a spawned object and + # check that it exists in the process. + query = search_object(objKeyStr) + commandTest.assertIsNotNone(query) + obj = query[0] + commandTest.assertIsNotNone(obj) + return obj + + # Tests "@spawn" without any arguments. + self.call(building.CmdSpawn(), " ", "Usage: @spawn") + + # Tests "@spawn " without specifying location. + self.call(building.CmdSpawn(), \ + "{'key':'goblin', 'typeclass':'evennia.DefaultCharacter'}", "Spawned goblin") + goblin = getObject(self, "goblin") + + # Tests that the spawned object's type is a DefaultCharacter. + self.assertIsInstance(goblin, DefaultCharacter) + + # Tests that the spawned object's location is the same as the caharacter's location, since + # we did not specify it. + self.assertEqual(goblin.location, self.char1.location) + goblin.delete() + + # Test "@spawn " with a location other than the character's. + spawnLoc = self.room2 + if spawnLoc == self.char1.location: + # Just to make sure we use a different location, in case someone changes + # char1's default location in the future... + spawnLoc = self.room1 + + self.call(building.CmdSpawn(), \ + "{'prototype':'GOBLIN', 'key':'goblin', 'location':'%s'}" \ + % spawnLoc.dbref, "Spawned goblin") + goblin = getObject(self, "goblin") + self.assertEqual(goblin.location, spawnLoc) + goblin.delete() + + # Tests "@spawn " + self.call(building.CmdSpawn(), "'BALL'", "Spawned Ball") + ball = getObject(self, "Ball") + self.assertEqual(ball.location, self.char1.location) + self.assertIsInstance(ball, DefaultObject) + ball.delete() + + # Tests "@spawn/noloc ..." without specifying a location. + # Location should be "None". + self.call(building.CmdSpawn(), "/noloc 'BALL'", "Spawned Ball") + ball = getObject(self, "Ball") + self.assertIsNone(ball.location) + ball.delete() + + # Tests "@spawn/noloc ...", but DO specify a location. + # Location should be the specified location. + self.call(building.CmdSpawn(), \ + "/noloc {'prototype':'BALL', 'location':'%s'}" \ + % spawnLoc.dbref, "Spawned Ball") + ball = getObject(self, "Ball") + self.assertEqual(ball.location, spawnLoc) + ball.delete() + + # test calling spawn with an invalid prototype. + self.call(building.CmdSpawn(), \ + "'NO_EXIST'", "No prototype named 'NO_EXIST'") + class TestComms(CommandTest): diff --git a/evennia/utils/test_prototypes.py b/evennia/utils/test_prototypes.py new file mode 100644 index 0000000000..5c79bad462 --- /dev/null +++ b/evennia/utils/test_prototypes.py @@ -0,0 +1,20 @@ +""" +test_prototypes + +This is meant to be used with Evennia unittest framework +to provide some named prototypes for use with various tests +(for example commands that accept named prototypes, not just +prototype dictionaries) +""" + +GOBLIN = { + "key" : "Goblin", \ + "typeclass" : "DefaultCharacter", \ + "desc" : "A goblin." +} + +BALL = { + "key" : "Ball", \ + "typeclass" : "DefaultObject", \ + "desc" : "A ball." +} diff --git a/evennia/utils/test_resources.py b/evennia/utils/test_resources.py index 47716e979e..f6785a2f1f 100644 --- a/evennia/utils/test_resources.py +++ b/evennia/utils/test_resources.py @@ -34,6 +34,8 @@ class EvenniaTest(TestCase): self.room1 = create.create_object(self.room_typeclass, key="Room", nohome=True) self.room1.db.desc = "room_desc" settings.DEFAULT_HOME = "#%i" % self.room1.id # we must have a default home + # Set up fake prototype module for allowing tests to use named prototypes. + settings.PROTOTYPE_MODULES = "evennia.utils.test_prototypes" self.room2 = create.create_object(self.room_typeclass, key="Room2") self.exit = create.create_object(self.exit_typeclass, key='out', location=self.room1, destination=self.room2) self.obj1 = create.create_object(self.object_typeclass, key="Obj", location=self.room1, home=self.room1) From 70fea693e640bb5c525500301c1cb29d172ff071 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 7 Oct 2017 11:27:47 +0200 Subject: [PATCH 2/2] Refactor utils test suite into utils/tests/ --- evennia/utils/evform.py | 2 +- evennia/utils/test_resources.py | 2 +- evennia/utils/tests.py | 532 ------------------ evennia/utils/tests/__init__.py | 0 evennia/utils/tests/data/__init__.py | 0 .../data/evform_example.py} | 0 .../data/prototypes_example.py} | 0 evennia/utils/tests/test_evform.py | 55 ++ evennia/utils/tests/test_evmenu.py | 25 + evennia/utils/tests/test_tagparsing.py | 277 +++++++++ evennia/utils/tests/test_utils.py | 188 +++++++ 11 files changed, 547 insertions(+), 534 deletions(-) delete mode 100644 evennia/utils/tests.py create mode 100644 evennia/utils/tests/__init__.py create mode 100644 evennia/utils/tests/data/__init__.py rename evennia/utils/{evform_test.py => tests/data/evform_example.py} (100%) rename evennia/utils/{test_prototypes.py => tests/data/prototypes_example.py} (100%) create mode 100644 evennia/utils/tests/test_evform.py create mode 100644 evennia/utils/tests/test_evmenu.py create mode 100644 evennia/utils/tests/test_tagparsing.py create mode 100644 evennia/utils/tests/test_utils.py diff --git a/evennia/utils/evform.py b/evennia/utils/evform.py index b601c8bfc5..55fa0ec9e2 100644 --- a/evennia/utils/evform.py +++ b/evennia/utils/evform.py @@ -429,7 +429,7 @@ class EvForm(object): def _test(): "test evform. This is used by the unittest system." - form = EvForm("evennia.utils.evform_test") + form = EvForm("evennia.utils.tests.data.evform_example") # add data to each tagged form cell form.map(cells={"AA": "|gTom the Bouncer", diff --git a/evennia/utils/test_resources.py b/evennia/utils/test_resources.py index f6785a2f1f..b4124b7219 100644 --- a/evennia/utils/test_resources.py +++ b/evennia/utils/test_resources.py @@ -35,7 +35,7 @@ class EvenniaTest(TestCase): self.room1.db.desc = "room_desc" settings.DEFAULT_HOME = "#%i" % self.room1.id # we must have a default home # Set up fake prototype module for allowing tests to use named prototypes. - settings.PROTOTYPE_MODULES = "evennia.utils.test_prototypes" + settings.PROTOTYPE_MODULES = "evennia.utils.tests.data.prototypes_example" self.room2 = create.create_object(self.room_typeclass, key="Room2") self.exit = create.create_object(self.exit_typeclass, key='out', location=self.room1, destination=self.room2) self.obj1 = create.create_object(self.object_typeclass, key="Obj", location=self.room1, home=self.room1) diff --git a/evennia/utils/tests.py b/evennia/utils/tests.py deleted file mode 100644 index 78ceb45a78..0000000000 --- a/evennia/utils/tests.py +++ /dev/null @@ -1,532 +0,0 @@ -from builtins import range - -import re - -try: - from django.utils.unittest import TestCase -except ImportError: - from django.test import TestCase - -from .ansi import ANSIString -from evennia import utils - -from django.conf import settings - - -class ANSIStringTestCase(TestCase): - def checker(self, ansi, raw, clean): - """ - Verifies the raw and clean strings of an ANSIString match expected - output. - """ - self.assertEqual(unicode(ansi.clean()), clean) - self.assertEqual(unicode(ansi.raw()), raw) - - def table_check(self, ansi, char, code): - """ - Verifies the indexes in an ANSIString match what they should. - """ - self.assertEqual(ansi._char_indexes, char) - self.assertEqual(ansi._code_indexes, code) - - def test_instance(self): - """ - Make sure the ANSIString is always constructed correctly. - """ - clean = u'This isA|r testTest' - encoded = u'\x1b[1m\x1b[32mThis is\x1b[1m\x1b[31mA|r test\x1b[0mTest\x1b[0m' - target = ANSIString(r'|gThis is|rA||r test|nTest|n') - char_table = [9, 10, 11, 12, 13, 14, 15, 25, 26, 27, 28, 29, 30, 31, 32, 37, 38, 39, 40] - code_table = [0, 1, 2, 3, 4, 5, 6, 7, 8, 16, 17, 18, 19, 20, 21, 22, 23, 24, 33, 34, 35, 36, 41, 42, 43, 44] - self.checker(target, encoded, clean) - self.table_check(target, char_table, code_table) - self.checker(ANSIString(target), encoded, clean) - self.table_check(ANSIString(target), char_table, code_table) - self.checker(ANSIString(encoded, decoded=True), encoded, clean) - self.table_check(ANSIString(encoded, decoded=True), char_table, - code_table) - self.checker(ANSIString('Test'), u'Test', u'Test') - self.table_check(ANSIString('Test'), [0, 1, 2, 3], []) - self.checker(ANSIString(''), u'', u'') - - def test_slice(self): - """ - Verifies that slicing an ANSIString results in expected color code - distribution. - """ - target = ANSIString(r'|gTest|rTest|n') - result = target[:3] - self.checker(result, u'\x1b[1m\x1b[32mTes', u'Tes') - result = target[:4] - self.checker(result, u'\x1b[1m\x1b[32mTest\x1b[1m\x1b[31m', u'Test') - result = target[:] - self.checker( - result, - u'\x1b[1m\x1b[32mTest\x1b[1m\x1b[31mTest\x1b[0m', - u'TestTest') - result = target[:-1] - self.checker( - result, - u'\x1b[1m\x1b[32mTest\x1b[1m\x1b[31mTes', - u'TestTes') - result = target[0:0] - self.checker( - result, - u'', - u'') - - def test_split(self): - """ - Verifies that re.split and .split behave similarly and that color - codes end up where they should. - """ - target = ANSIString("|gThis is |nA split string|g") - first = (u'\x1b[1m\x1b[32mThis is \x1b[0m', u'This is ') - second = (u'\x1b[1m\x1b[32m\x1b[0m split string\x1b[1m\x1b[32m', - u' split string') - re_split = re.split('A', target) - normal_split = target.split('A') - self.assertEqual(re_split, normal_split) - self.assertEqual(len(normal_split), 2) - self.checker(normal_split[0], *first) - self.checker(normal_split[1], *second) - - def test_join(self): - """ - Verify that joining a set of ANSIStrings works. - """ - # This isn't the desired behavior, but the expected one. Python - # concatenates the in-memory representation with the built-in string's - # join. - l = [ANSIString("|gTest|r") for _ in range(0, 3)] - # Force the generator to be evaluated. - result = "".join(l) - self.assertEqual(unicode(result), u'TestTestTest') - result = ANSIString("").join(l) - self.checker(result, u'\x1b[1m\x1b[32mTest\x1b[1m\x1b[31m\x1b[1m\x1b' - u'[32mTest\x1b[1m\x1b[31m\x1b[1m\x1b[32mTest' - u'\x1b[1m\x1b[31m', u'TestTestTest') - - def test_len(self): - """ - Make sure that length reporting on ANSIStrings does not include - ANSI codes. - """ - self.assertEqual(len(ANSIString('|gTest|n')), 4) - - def test_capitalize(self): - """ - Make sure that capitalization works. This is the simplest of the - _transform functions. - """ - target = ANSIString('|gtest|n') - result = u'\x1b[1m\x1b[32mTest\x1b[0m' - self.checker(target.capitalize(), result, u'Test') - - def test_mxp_agnostic(self): - """ - Make sure MXP tags are not treated like ANSI codes, but normal text. - """ - mxp1 = "|lclook|ltat|le" - mxp2 = "Start to |lclook here|ltclick somewhere here|le first" - self.assertEqual(15, len(ANSIString(mxp1))) - self.assertEqual(53, len(ANSIString(mxp2))) - # These would indicate an issue with the tables. - self.assertEqual(len(ANSIString(mxp1)), len(ANSIString(mxp1).split("\n")[0])) - self.assertEqual(len(ANSIString(mxp2)), len(ANSIString(mxp2).split("\n")[0])) - self.assertEqual(mxp1, ANSIString(mxp1)) - self.assertEqual(mxp2, unicode(ANSIString(mxp2))) - - def test_add(self): - """ - Verify concatenation works correctly. - """ - a = ANSIString("|gTest") - b = ANSIString("|cString|n") - c = a + b - result = u'\x1b[1m\x1b[32mTest\x1b[1m\x1b[36mString\x1b[0m' - self.checker(c, result, u'TestString') - char_table = [9, 10, 11, 12, 22, 23, 24, 25, 26, 27] - code_table = [0, 1, 2, 3, 4, 5, 6, 7, 8, 13, 14, 15, 16, 17, 18, 19, 20, 21, 28, 29, 30, 31] - self.table_check(c, char_table, code_table) - - def test_strip(self): - """ - Test the ansi-aware .strip() methods - """ - a = ANSIString(" |r Test of stuff |b with spaces |n ") - b = ANSIString("|r|b") - self.assertEqual(a.strip(), ANSIString("|rTest of stuff |b with spaces|n")) - self.assertEqual(a.lstrip(), ANSIString("|rTest of stuff |b with spaces |n ")) - self.assertEqual(a.rstrip(), ANSIString(" |r Test of stuff |b with spaces|n")) - self.assertEqual(b.strip(), b) - - -class TestIsIter(TestCase): - def test_is_iter(self): - self.assertEqual(True, utils.is_iter([1, 2, 3, 4])) - self.assertEqual(False, utils.is_iter("This is not an iterable")) - - -class TestCrop(TestCase): - def test_crop(self): - # No text, return no text - self.assertEqual("", utils.crop("", width=10, suffix="[...]")) - # Input length equal to max width, no crop - self.assertEqual("0123456789", utils.crop("0123456789", width=10, suffix="[...]")) - # Input length greater than max width, crop (suffix included in width) - self.assertEqual("0123[...]", utils.crop("0123456789", width=9, suffix="[...]")) - # Input length less than desired width, no crop - self.assertEqual("0123", utils.crop("0123", width=9, suffix="[...]")) - # Width too small or equal to width of suffix - self.assertEqual("012", utils.crop("0123", width=3, suffix="[...]")) - self.assertEqual("01234", utils.crop("0123456", width=5, suffix="[...]")) - - -class TestDedent(TestCase): - def test_dedent(self): - # Empty string, return empty string - self.assertEqual("", utils.dedent("")) - # No leading whitespace - self.assertEqual("TestDedent", utils.dedent("TestDedent")) - # Leading whitespace, single line - self.assertEqual("TestDedent", utils.dedent(" TestDedent")) - # Leading whitespace, multi line - input_string = " hello\n world" - expected_string = "hello\nworld" - self.assertEqual(expected_string, utils.dedent(input_string)) - - -class TestListToString(TestCase): - """ - Default function header from utils.py: - list_to_string(inlist, endsep="and", addquote=False) - - Examples: - no endsep: - [1,2,3] -> '1, 2, 3' - with endsep=='and': - [1,2,3] -> '1, 2 and 3' - with addquote and endsep - [1,2,3] -> '"1", "2" and "3"' - """ - - def test_list_to_string(self): - 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], endsep="", addquote=True)) - self.assertEqual('1, 2 and 3', utils.list_to_string([1, 2, 3])) - self.assertEqual('"1", "2" and "3"', utils.list_to_string([1, 2, 3], endsep="and", addquote=True)) - - -class TestMLen(TestCase): - """ - Verifies that m_len behaves like len in all situations except those - where MXP may be involved. - """ - - def test_non_mxp_string(self): - self.assertEqual(utils.m_len('Test_string'), 11) - - def test_mxp_string(self): - self.assertEqual(utils.m_len('|lclook|ltat|le'), 2) - - def test_mxp_ansi_string(self): - self.assertEqual(utils.m_len(ANSIString('|lcl|gook|ltat|le|n')), 2) - - def test_non_mxp_ansi_string(self): - self.assertEqual(utils.m_len(ANSIString('|gHello|n')), 5) - - def test_list(self): - self.assertEqual(utils.m_len([None, None]), 2) - - def test_dict(self): - self.assertEqual(utils.m_len({'hello': True, 'Goodbye': False}), 2) - - -from .text2html import TextToHTMLparser - - -class TestTextToHTMLparser(TestCase): - def setUp(self): - self.parser = TextToHTMLparser() - - def tearDown(self): - del self.parser - - def test_url_scheme_ftp(self): - self.assertEqual(self.parser.convert_urls('ftp.example.com'), - 'ftp.example.com') - - def test_url_scheme_www(self): - self.assertEqual(self.parser.convert_urls('www.example.com'), - 'www.example.com') - - def test_url_scheme_ftpproto(self): - self.assertEqual(self.parser.convert_urls('ftp://ftp.example.com'), - 'ftp://ftp.example.com') - - def test_url_scheme_http(self): - self.assertEqual(self.parser.convert_urls('http://example.com'), - 'http://example.com') - - def test_url_scheme_https(self): - self.assertEqual(self.parser.convert_urls('https://example.com'), - 'https://example.com') - - def test_url_chars_slash(self): - self.assertEqual(self.parser.convert_urls('www.example.com/homedir'), - 'www.example.com/homedir') - - def test_url_chars_colon(self): - self.assertEqual(self.parser.convert_urls('https://example.com:8000/login/'), - '' - 'https://example.com:8000/login/') - - def test_url_chars_querystring(self): - self.assertEqual(self.parser.convert_urls('https://example.com/submitform?field1=val1+val3&field2=val2'), - '' - 'https://example.com/submitform?field1=val1+val3&field2=val2') - - def test_url_chars_anchor(self): - self.assertEqual(self.parser.convert_urls('http://www.example.com/menu#section_1'), - '' - 'http://www.example.com/menu#section_1') - - def test_url_chars_exclam(self): - self.assertEqual(self.parser.convert_urls('https://groups.google.com/forum/' - '?fromgroups#!categories/evennia/ainneve'), - 'https://groups.google.com/forum/?fromgroups#!categories/evennia/ainneve') - - def test_url_edge_leadingw(self): - self.assertEqual(self.parser.convert_urls('wwww.example.com'), - 'wwww.example.com') - - def test_url_edge_following_period_eol(self): - self.assertEqual(self.parser.convert_urls('www.example.com.'), - 'www.example.com.') - - def test_url_edge_following_period(self): - self.assertEqual(self.parser.convert_urls('see www.example.com. '), - 'see www.example.com. ') - - def test_url_edge_brackets(self): - self.assertEqual(self.parser.convert_urls('[http://example.com/]'), - '[http://example.com/]') - - def test_url_edge_multiline(self): - self.assertEqual(self.parser.convert_urls(' * http://example.com/info\n * bullet'), - ' * ' - 'http://example.com/info\n * bullet') - - def test_url_edge_following_htmlentity(self): - self.assertEqual(self.parser.convert_urls('http://example.com/info<span>'), - 'http://example.com/info<span>') - - def test_url_edge_surrounded_spans(self): - self.assertEqual(self.parser.convert_urls('http://example.com/'), - '' - 'http://example.com/') - - -from evennia.utils import evmenu -from mock import Mock - - -class TestEvMenu(TestCase): - "Run the EvMenu test." - - def setUp(self): - self.caller = Mock() - self.caller.msg = Mock() - self.menu = evmenu.EvMenu(self.caller, "evennia.utils.evmenu", startnode="test_start_node", - persistent=True, cmdset_mergetype="Replace", testval="val", testval2="val2") - - def test_kwargsave(self): - self.assertTrue(hasattr(self.menu, "testval")) - self.assertTrue(hasattr(self.menu, "testval2")) - - -from evennia.utils import inlinefuncs - - -class TestInlineFuncs(TestCase): - """Test the nested inlinefunc module""" - - def test_nofunc(self): - self.assertEqual(inlinefuncs.parse_inlinefunc( - "as$382ewrw w we w werw,|44943}"), - "as$382ewrw w we w werw,|44943}") - - def test_incomplete(self): - self.assertEqual(inlinefuncs.parse_inlinefunc( - "testing $blah{without an ending."), - "testing $blah{without an ending.") - - def test_single_func(self): - self.assertEqual(inlinefuncs.parse_inlinefunc( - "this is a test with $pad(centered, 20) text in it."), - "this is a test with centered text in it.") - - def test_nested(self): - self.assertEqual(inlinefuncs.parse_inlinefunc( - "this $crop(is a test with $pad(padded, 20) text in $pad(pad2, 10) a crop, 80)"), - "this is a test with padded text in pad2 a crop") - - def test_escaped(self): - self.assertEqual(inlinefuncs.parse_inlinefunc( - "this should be $pad('''escaped,''' and '''instead,''' cropped $crop(with a long,5) text., 80)"), - "this should be escaped, and instead, cropped with text. ") - - def test_escaped2(self): - self.assertEqual(inlinefuncs.parse_inlinefunc( - 'this should be $pad("""escaped,""" and """instead,""" cropped $crop(with a long,5) text., 80)'), - "this should be escaped, and instead, cropped with text. ") - - -from evennia.utils import evform - - -class TestEvForm(TestCase): - def test_form(self): - self.maxDiff = None - self.assertEqual(evform._test(), - u'.------------------------------------------------.\n' - u'| |\n' - u'| Name: \x1b[0m\x1b[1m\x1b[32mTom\x1b[1m\x1b[32m \x1b' - u'[1m\x1b[32mthe\x1b[1m\x1b[32m \x1b[0m \x1b[0m ' - u'Account: \x1b[0m\x1b[1m\x1b[33mGriatch ' - u'\x1b[0m\x1b[0m\x1b[1m\x1b[32m\x1b[1m\x1b[32m\x1b[1m\x1b[32m\x1b[1m\x1b[32m\x1b[0m\x1b[0m ' - u'|\n' - u'| \x1b[0m\x1b[1m\x1b[32mBouncer\x1b[0m \x1b[0m |\n' - u'| |\n' - u' >----------------------------------------------<\n' - u'| |\n' - u'| Desc: \x1b[0mA sturdy \x1b[0m \x1b[0m' - u' STR: \x1b[0m12 \x1b[0m\x1b[0m\x1b[0m\x1b[0m' - u' DEX: \x1b[0m10 \x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n' - u'| \x1b[0mfellow\x1b[0m \x1b[0m' - u' INT: \x1b[0m5 \x1b[0m\x1b[0m\x1b[0m\x1b[0m' - u' STA: \x1b[0m18 \x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n' - u'| \x1b[0m \x1b[0m' - u' LUC: \x1b[0m10 \x1b[0m\x1b[0m\x1b[0m' - u' MAG: \x1b[0m3 \x1b[0m\x1b[0m\x1b[0m |\n' - u'| |\n' - u' >----------.-----------------------------------<\n' - u'| | |\n' - u'| \x1b[0mHP\x1b[0m|\x1b[0mMV \x1b[0m|\x1b[0mMP\x1b[0m ' - u'| \x1b[0mSkill \x1b[0m|\x1b[0mValue \x1b[0m' - u'|\x1b[0mExp \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n' - u'| ~~+~~~+~~ | ~~~~~~~~~~~+~~~~~~~~~~+~~~~~~~~~~~ |\n' - u'| \x1b[0m**\x1b[0m|\x1b[0m***\x1b[0m\x1b[0m|\x1b[0m**\x1b[0m\x1b[0m ' - u'| \x1b[0mShooting \x1b[0m|\x1b[0m12 \x1b[0m' - u'|\x1b[0m550/1200 \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n' - u'| \x1b[0m \x1b[0m|\x1b[0m**\x1b[0m \x1b[0m|\x1b[0m*\x1b[0m \x1b[0m ' - u'| \x1b[0mHerbalism \x1b[0m|\x1b[0m14 \x1b[0m' - u'|\x1b[0m990/1400 \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n' - u'| \x1b[0m \x1b[0m|\x1b[0m \x1b[0m|\x1b[0m \x1b[0m ' - u'| \x1b[0mSmithing \x1b[0m|\x1b[0m9 \x1b[0m' - u'|\x1b[0m205/900 \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n' - u'| | |\n' - u' -----------`-------------------------------------\n') - - def test_ansi_escape(self): - # note that in a msg() call, the result would be the correct |-----, - # in a print, ansi only gets called once, so ||----- is the result - self.assertEqual(unicode(evform.EvForm(form={"FORM": "\n||-----"})), "||-----") - - -class TestTimeformat(TestCase): - """ - Default function header from utils.py: - time_format(seconds, style=0) - - """ - - def test_style_0(self): - """Test the style 0 of time_format.""" - self.assertEqual(utils.time_format(0, 0), "00:00") - self.assertEqual(utils.time_format(28, 0), "00:00") - self.assertEqual(utils.time_format(92, 0), "00:01") - self.assertEqual(utils.time_format(300, 0), "00:05") - self.assertEqual(utils.time_format(660, 0), "00:11") - self.assertEqual(utils.time_format(3600, 0), "01:00") - self.assertEqual(utils.time_format(3725, 0), "01:02") - self.assertEqual(utils.time_format(86350, 0), "23:59") - self.assertEqual(utils.time_format(86800, 0), "1d 00:06") - self.assertEqual(utils.time_format(130800, 0), "1d 12:20") - self.assertEqual(utils.time_format(530800, 0), "6d 03:26") - - def test_style_1(self): - """Test the style 1 of time_format.""" - self.assertEqual(utils.time_format(0, 1), "0s") - self.assertEqual(utils.time_format(28, 1), "28s") - self.assertEqual(utils.time_format(92, 1), "1m") - self.assertEqual(utils.time_format(300, 1), "5m") - self.assertEqual(utils.time_format(660, 1), "11m") - self.assertEqual(utils.time_format(3600, 1), "1h") - self.assertEqual(utils.time_format(3725, 1), "1h") - self.assertEqual(utils.time_format(86350, 1), "23h") - self.assertEqual(utils.time_format(86800, 1), "1d") - self.assertEqual(utils.time_format(130800, 1), "1d") - self.assertEqual(utils.time_format(530800, 1), "6d") - - def test_style_2(self): - """Test the style 2 of time_format.""" - self.assertEqual(utils.time_format(0, 2), "0 minutes") - self.assertEqual(utils.time_format(28, 2), "0 minutes") - self.assertEqual(utils.time_format(92, 2), "1 minute") - self.assertEqual(utils.time_format(300, 2), "5 minutes") - self.assertEqual(utils.time_format(660, 2), "11 minutes") - self.assertEqual(utils.time_format(3600, 2), "1 hour, 0 minutes") - self.assertEqual(utils.time_format(3725, 2), "1 hour, 2 minutes") - self.assertEqual(utils.time_format(86350, 2), "23 hours, 59 minutes") - self.assertEqual(utils.time_format(86800, 2), - "1 day, 0 hours, 6 minutes") - self.assertEqual(utils.time_format(130800, 2), - "1 day, 12 hours, 20 minutes") - self.assertEqual(utils.time_format(530800, 2), - "6 days, 3 hours, 26 minutes") - - def test_style_3(self): - """Test the style 3 of time_format.""" - self.assertEqual(utils.time_format(0, 3), "") - self.assertEqual(utils.time_format(28, 3), "28 seconds") - self.assertEqual(utils.time_format(92, 3), "1 minute 32 seconds") - self.assertEqual(utils.time_format(300, 3), "5 minutes 0 seconds") - self.assertEqual(utils.time_format(660, 3), "11 minutes 0 seconds") - self.assertEqual(utils.time_format(3600, 3), - "1 hour, 0 minutes") - self.assertEqual(utils.time_format(3725, 3), - "1 hour, 2 minutes 5 seconds") - self.assertEqual(utils.time_format(86350, 3), - "23 hours, 59 minutes 10 seconds") - self.assertEqual(utils.time_format(86800, 3), - "1 day, 0 hours, 6 minutes 40 seconds") - self.assertEqual(utils.time_format(130800, 3), - "1 day, 12 hours, 20 minutes 0 seconds") - self.assertEqual(utils.time_format(530800, 3), - "6 days, 3 hours, 26 minutes 40 seconds") - - def test_style_4(self): - """Test the style 4 of time_format.""" - self.assertEqual(utils.time_format(0, 4), "0 seconds") - self.assertEqual(utils.time_format(28, 4), "28 seconds") - self.assertEqual(utils.time_format(92, 4), "a minute") - self.assertEqual(utils.time_format(300, 4), "5 minutes") - self.assertEqual(utils.time_format(660, 4), "11 minutes") - self.assertEqual(utils.time_format(3600, 4), "an hour") - self.assertEqual(utils.time_format(3725, 4), "an hour") - self.assertEqual(utils.time_format(86350, 4), "23 hours") - self.assertEqual(utils.time_format(86800, 4), "a day") - self.assertEqual(utils.time_format(130800, 4), "a day") - self.assertEqual(utils.time_format(530800, 4), "6 days") - self.assertEqual(utils.time_format(3030800, 4), "a month") - self.assertEqual(utils.time_format(7030800, 4), "2 months") - self.assertEqual(utils.time_format(40030800, 4), "a year") - self.assertEqual(utils.time_format(90030800, 4), "2 years") - - def test_unknown_format(self): - """Test that unknown formats raise exceptions.""" - self.assertRaises(ValueError, utils.time_format, 0, 5) - self.assertRaises(ValueError, utils.time_format, 0, "u") diff --git a/evennia/utils/tests/__init__.py b/evennia/utils/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/evennia/utils/tests/data/__init__.py b/evennia/utils/tests/data/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/evennia/utils/evform_test.py b/evennia/utils/tests/data/evform_example.py similarity index 100% rename from evennia/utils/evform_test.py rename to evennia/utils/tests/data/evform_example.py diff --git a/evennia/utils/test_prototypes.py b/evennia/utils/tests/data/prototypes_example.py similarity index 100% rename from evennia/utils/test_prototypes.py rename to evennia/utils/tests/data/prototypes_example.py diff --git a/evennia/utils/tests/test_evform.py b/evennia/utils/tests/test_evform.py new file mode 100644 index 0000000000..e6a0d26049 --- /dev/null +++ b/evennia/utils/tests/test_evform.py @@ -0,0 +1,55 @@ +""" +Unit tests for the EvForm text form generator + +""" +from django.test import TestCase +from evennia.utils import evform + + +class TestEvForm(TestCase): + def test_form(self): + self.maxDiff = None + self.assertEqual(evform._test(), + u'.------------------------------------------------.\n' + u'| |\n' + u'| Name: \x1b[0m\x1b[1m\x1b[32mTom\x1b[1m\x1b[32m \x1b' + u'[1m\x1b[32mthe\x1b[1m\x1b[32m \x1b[0m \x1b[0m ' + u'Account: \x1b[0m\x1b[1m\x1b[33mGriatch ' + u'\x1b[0m\x1b[0m\x1b[1m\x1b[32m\x1b[1m\x1b[32m\x1b[1m\x1b[32m\x1b[1m\x1b[32m\x1b[0m\x1b[0m ' + u'|\n' + u'| \x1b[0m\x1b[1m\x1b[32mBouncer\x1b[0m \x1b[0m |\n' + u'| |\n' + u' >----------------------------------------------<\n' + u'| |\n' + u'| Desc: \x1b[0mA sturdy \x1b[0m \x1b[0m' + u' STR: \x1b[0m12 \x1b[0m\x1b[0m\x1b[0m\x1b[0m' + u' DEX: \x1b[0m10 \x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n' + u'| \x1b[0mfellow\x1b[0m \x1b[0m' + u' INT: \x1b[0m5 \x1b[0m\x1b[0m\x1b[0m\x1b[0m' + u' STA: \x1b[0m18 \x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n' + u'| \x1b[0m \x1b[0m' + u' LUC: \x1b[0m10 \x1b[0m\x1b[0m\x1b[0m' + u' MAG: \x1b[0m3 \x1b[0m\x1b[0m\x1b[0m |\n' + u'| |\n' + u' >----------.-----------------------------------<\n' + u'| | |\n' + u'| \x1b[0mHP\x1b[0m|\x1b[0mMV \x1b[0m|\x1b[0mMP\x1b[0m ' + u'| \x1b[0mSkill \x1b[0m|\x1b[0mValue \x1b[0m' + u'|\x1b[0mExp \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n' + u'| ~~+~~~+~~ | ~~~~~~~~~~~+~~~~~~~~~~+~~~~~~~~~~~ |\n' + u'| \x1b[0m**\x1b[0m|\x1b[0m***\x1b[0m\x1b[0m|\x1b[0m**\x1b[0m\x1b[0m ' + u'| \x1b[0mShooting \x1b[0m|\x1b[0m12 \x1b[0m' + u'|\x1b[0m550/1200 \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n' + u'| \x1b[0m \x1b[0m|\x1b[0m**\x1b[0m \x1b[0m|\x1b[0m*\x1b[0m \x1b[0m ' + u'| \x1b[0mHerbalism \x1b[0m|\x1b[0m14 \x1b[0m' + u'|\x1b[0m990/1400 \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n' + u'| \x1b[0m \x1b[0m|\x1b[0m \x1b[0m|\x1b[0m \x1b[0m ' + u'| \x1b[0mSmithing \x1b[0m|\x1b[0m9 \x1b[0m' + u'|\x1b[0m205/900 \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n' + u'| | |\n' + u' -----------`-------------------------------------\n') + + def test_ansi_escape(self): + # note that in a msg() call, the result would be the correct |-----, + # in a print, ansi only gets called once, so ||----- is the result + self.assertEqual(unicode(evform.EvForm(form={"FORM": "\n||-----"})), "||-----") diff --git a/evennia/utils/tests/test_evmenu.py b/evennia/utils/tests/test_evmenu.py new file mode 100644 index 0000000000..4436fdccd6 --- /dev/null +++ b/evennia/utils/tests/test_evmenu.py @@ -0,0 +1,25 @@ +""" +Unit tests for the EvMenu system + +TODO: This need expansion. + +""" + +from django.test import TestCase +from evennia.utils import evmenu +from mock import Mock + + +class TestEvMenu(TestCase): + "Run the EvMenu testing." + + def setUp(self): + self.caller = Mock() + self.caller.msg = Mock() + self.menu = evmenu.EvMenu(self.caller, "evennia.utils.evmenu", startnode="test_start_node", + persistent=True, cmdset_mergetype="Replace", testval="val", + testval2="val2") + + def test_kwargsave(self): + self.assertTrue(hasattr(self.menu, "testval")) + self.assertTrue(hasattr(self.menu, "testval2")) diff --git a/evennia/utils/tests/test_tagparsing.py b/evennia/utils/tests/test_tagparsing.py new file mode 100644 index 0000000000..a2f07af204 --- /dev/null +++ b/evennia/utils/tests/test_tagparsing.py @@ -0,0 +1,277 @@ +""" +Unit tests for all sorts of inline text-tag parsing, like ANSI, html conversion, inlinefuncs etc + +""" +import re +from django.test import TestCase +from evennia.utils.ansi import ANSIString +from evennia.utils.text2html import TextToHTMLparser +from evennia.utils import inlinefuncs + + +class ANSIStringTestCase(TestCase): + def checker(self, ansi, raw, clean): + """ + Verifies the raw and clean strings of an ANSIString match expected + output. + """ + self.assertEqual(unicode(ansi.clean()), clean) + self.assertEqual(unicode(ansi.raw()), raw) + + def table_check(self, ansi, char, code): + """ + Verifies the indexes in an ANSIString match what they should. + """ + self.assertEqual(ansi._char_indexes, char) + self.assertEqual(ansi._code_indexes, code) + + def test_instance(self): + """ + Make sure the ANSIString is always constructed correctly. + """ + clean = u'This isA|r testTest' + encoded = u'\x1b[1m\x1b[32mThis is\x1b[1m\x1b[31mA|r test\x1b[0mTest\x1b[0m' + target = ANSIString(r'|gThis is|rA||r test|nTest|n') + char_table = [9, 10, 11, 12, 13, 14, 15, 25, 26, 27, 28, 29, 30, 31, 32, 37, 38, 39, 40] + code_table = [0, 1, 2, 3, 4, 5, 6, 7, 8, 16, 17, 18, 19, 20, 21, 22, 23, 24, 33, 34, 35, 36, 41, 42, 43, 44] + self.checker(target, encoded, clean) + self.table_check(target, char_table, code_table) + self.checker(ANSIString(target), encoded, clean) + self.table_check(ANSIString(target), char_table, code_table) + self.checker(ANSIString(encoded, decoded=True), encoded, clean) + self.table_check(ANSIString(encoded, decoded=True), char_table, + code_table) + self.checker(ANSIString('Test'), u'Test', u'Test') + self.table_check(ANSIString('Test'), [0, 1, 2, 3], []) + self.checker(ANSIString(''), u'', u'') + + def test_slice(self): + """ + Verifies that slicing an ANSIString results in expected color code + distribution. + """ + target = ANSIString(r'|gTest|rTest|n') + result = target[:3] + self.checker(result, u'\x1b[1m\x1b[32mTes', u'Tes') + result = target[:4] + self.checker(result, u'\x1b[1m\x1b[32mTest\x1b[1m\x1b[31m', u'Test') + result = target[:] + self.checker( + result, + u'\x1b[1m\x1b[32mTest\x1b[1m\x1b[31mTest\x1b[0m', + u'TestTest') + result = target[:-1] + self.checker( + result, + u'\x1b[1m\x1b[32mTest\x1b[1m\x1b[31mTes', + u'TestTes') + result = target[0:0] + self.checker( + result, + u'', + u'') + + def test_split(self): + """ + Verifies that re.split and .split behave similarly and that color + codes end up where they should. + """ + target = ANSIString("|gThis is |nA split string|g") + first = (u'\x1b[1m\x1b[32mThis is \x1b[0m', u'This is ') + second = (u'\x1b[1m\x1b[32m\x1b[0m split string\x1b[1m\x1b[32m', + u' split string') + re_split = re.split('A', target) + normal_split = target.split('A') + self.assertEqual(re_split, normal_split) + self.assertEqual(len(normal_split), 2) + self.checker(normal_split[0], *first) + self.checker(normal_split[1], *second) + + def test_join(self): + """ + Verify that joining a set of ANSIStrings works. + """ + # This isn't the desired behavior, but the expected one. Python + # concatenates the in-memory representation with the built-in string's + # join. + l = [ANSIString("|gTest|r") for _ in range(0, 3)] + # Force the generator to be evaluated. + result = "".join(l) + self.assertEqual(unicode(result), u'TestTestTest') + result = ANSIString("").join(l) + self.checker(result, u'\x1b[1m\x1b[32mTest\x1b[1m\x1b[31m\x1b[1m\x1b' + u'[32mTest\x1b[1m\x1b[31m\x1b[1m\x1b[32mTest' + u'\x1b[1m\x1b[31m', u'TestTestTest') + + def test_len(self): + """ + Make sure that length reporting on ANSIStrings does not include + ANSI codes. + """ + self.assertEqual(len(ANSIString('|gTest|n')), 4) + + def test_capitalize(self): + """ + Make sure that capitalization works. This is the simplest of the + _transform functions. + """ + target = ANSIString('|gtest|n') + result = u'\x1b[1m\x1b[32mTest\x1b[0m' + self.checker(target.capitalize(), result, u'Test') + + def test_mxp_agnostic(self): + """ + Make sure MXP tags are not treated like ANSI codes, but normal text. + """ + mxp1 = "|lclook|ltat|le" + mxp2 = "Start to |lclook here|ltclick somewhere here|le first" + self.assertEqual(15, len(ANSIString(mxp1))) + self.assertEqual(53, len(ANSIString(mxp2))) + # These would indicate an issue with the tables. + self.assertEqual(len(ANSIString(mxp1)), len(ANSIString(mxp1).split("\n")[0])) + self.assertEqual(len(ANSIString(mxp2)), len(ANSIString(mxp2).split("\n")[0])) + self.assertEqual(mxp1, ANSIString(mxp1)) + self.assertEqual(mxp2, unicode(ANSIString(mxp2))) + + def test_add(self): + """ + Verify concatenation works correctly. + """ + a = ANSIString("|gTest") + b = ANSIString("|cString|n") + c = a + b + result = u'\x1b[1m\x1b[32mTest\x1b[1m\x1b[36mString\x1b[0m' + self.checker(c, result, u'TestString') + char_table = [9, 10, 11, 12, 22, 23, 24, 25, 26, 27] + code_table = [0, 1, 2, 3, 4, 5, 6, 7, 8, 13, 14, 15, 16, 17, 18, 19, 20, 21, 28, 29, 30, 31] + self.table_check(c, char_table, code_table) + + def test_strip(self): + """ + Test the ansi-aware .strip() methods + """ + a = ANSIString(" |r Test of stuff |b with spaces |n ") + b = ANSIString("|r|b") + self.assertEqual(a.strip(), ANSIString("|rTest of stuff |b with spaces|n")) + self.assertEqual(a.lstrip(), ANSIString("|rTest of stuff |b with spaces |n ")) + self.assertEqual(a.rstrip(), ANSIString(" |r Test of stuff |b with spaces|n")) + self.assertEqual(b.strip(), b) + + +class TestTextToHTMLparser(TestCase): + def setUp(self): + self.parser = TextToHTMLparser() + + def tearDown(self): + del self.parser + + def test_url_scheme_ftp(self): + self.assertEqual(self.parser.convert_urls('ftp.example.com'), + 'ftp.example.com') + + def test_url_scheme_www(self): + self.assertEqual(self.parser.convert_urls('www.example.com'), + 'www.example.com') + + def test_url_scheme_ftpproto(self): + self.assertEqual(self.parser.convert_urls('ftp://ftp.example.com'), + 'ftp://ftp.example.com') + + def test_url_scheme_http(self): + self.assertEqual(self.parser.convert_urls('http://example.com'), + 'http://example.com') + + def test_url_scheme_https(self): + self.assertEqual(self.parser.convert_urls('https://example.com'), + 'https://example.com') + + def test_url_chars_slash(self): + self.assertEqual(self.parser.convert_urls('www.example.com/homedir'), + 'www.example.com/homedir') + + def test_url_chars_colon(self): + self.assertEqual(self.parser.convert_urls('https://example.com:8000/login/'), + '' + 'https://example.com:8000/login/') + + def test_url_chars_querystring(self): + self.assertEqual(self.parser.convert_urls('https://example.com/submitform?field1=val1+val3&field2=val2'), + '' + 'https://example.com/submitform?field1=val1+val3&field2=val2') + + def test_url_chars_anchor(self): + self.assertEqual(self.parser.convert_urls('http://www.example.com/menu#section_1'), + '' + 'http://www.example.com/menu#section_1') + + def test_url_chars_exclam(self): + self.assertEqual(self.parser.convert_urls('https://groups.google.com/forum/' + '?fromgroups#!categories/evennia/ainneve'), + 'https://groups.google.com/forum/?fromgroups#!categories/evennia/ainneve') + + def test_url_edge_leadingw(self): + self.assertEqual(self.parser.convert_urls('wwww.example.com'), + 'wwww.example.com') + + def test_url_edge_following_period_eol(self): + self.assertEqual(self.parser.convert_urls('www.example.com.'), + 'www.example.com.') + + def test_url_edge_following_period(self): + self.assertEqual(self.parser.convert_urls('see www.example.com. '), + 'see www.example.com. ') + + def test_url_edge_brackets(self): + self.assertEqual(self.parser.convert_urls('[http://example.com/]'), + '[http://example.com/]') + + def test_url_edge_multiline(self): + self.assertEqual(self.parser.convert_urls(' * http://example.com/info\n * bullet'), + ' * ' + 'http://example.com/info\n * bullet') + + def test_url_edge_following_htmlentity(self): + self.assertEqual(self.parser.convert_urls('http://example.com/info<span>'), + 'http://example.com/info<span>') + + def test_url_edge_surrounded_spans(self): + self.assertEqual(self.parser.convert_urls('http://example.com/'), + '' + 'http://example.com/') + + +class TestInlineFuncs(TestCase): + """Test the nested inlinefunc module""" + + def test_nofunc(self): + self.assertEqual(inlinefuncs.parse_inlinefunc( + "as$382ewrw w we w werw,|44943}"), + "as$382ewrw w we w werw,|44943}") + + def test_incomplete(self): + self.assertEqual(inlinefuncs.parse_inlinefunc( + "testing $blah{without an ending."), + "testing $blah{without an ending.") + + def test_single_func(self): + self.assertEqual(inlinefuncs.parse_inlinefunc( + "this is a test with $pad(centered, 20) text in it."), + "this is a test with centered text in it.") + + def test_nested(self): + self.assertEqual(inlinefuncs.parse_inlinefunc( + "this $crop(is a test with $pad(padded, 20) text in $pad(pad2, 10) a crop, 80)"), + "this is a test with padded text in pad2 a crop") + + def test_escaped(self): + self.assertEqual(inlinefuncs.parse_inlinefunc( + "this should be $pad('''escaped,''' and '''instead,''' cropped $crop(with a long,5) text., 80)"), + "this should be escaped, and instead, cropped with text. ") + + def test_escaped2(self): + self.assertEqual(inlinefuncs.parse_inlinefunc( + 'this should be $pad("""escaped,""" and """instead,""" cropped $crop(with a long,5) text., 80)'), + "this should be escaped, and instead, cropped with text. ") + + diff --git a/evennia/utils/tests/test_utils.py b/evennia/utils/tests/test_utils.py new file mode 100644 index 0000000000..d2e42c4169 --- /dev/null +++ b/evennia/utils/tests/test_utils.py @@ -0,0 +1,188 @@ +""" +Unit tests for the utilities of the evennia.utils.utils module. + +TODO: Not nearly all utilities are covered yet. + +""" + +from django.test import TestCase + +from evennia.utils.ansi import ANSIString +from evennia.utils import utils + + +class TestIsIter(TestCase): + def test_is_iter(self): + self.assertEqual(True, utils.is_iter([1, 2, 3, 4])) + self.assertEqual(False, utils.is_iter("This is not an iterable")) + + +class TestCrop(TestCase): + def test_crop(self): + # No text, return no text + self.assertEqual("", utils.crop("", width=10, suffix="[...]")) + # Input length equal to max width, no crop + self.assertEqual("0123456789", utils.crop("0123456789", width=10, suffix="[...]")) + # Input length greater than max width, crop (suffix included in width) + self.assertEqual("0123[...]", utils.crop("0123456789", width=9, suffix="[...]")) + # Input length less than desired width, no crop + self.assertEqual("0123", utils.crop("0123", width=9, suffix="[...]")) + # Width too small or equal to width of suffix + self.assertEqual("012", utils.crop("0123", width=3, suffix="[...]")) + self.assertEqual("01234", utils.crop("0123456", width=5, suffix="[...]")) + + +class TestDedent(TestCase): + def test_dedent(self): + # Empty string, return empty string + self.assertEqual("", utils.dedent("")) + # No leading whitespace + self.assertEqual("TestDedent", utils.dedent("TestDedent")) + # Leading whitespace, single line + self.assertEqual("TestDedent", utils.dedent(" TestDedent")) + # Leading whitespace, multi line + input_string = " hello\n world" + expected_string = "hello\nworld" + self.assertEqual(expected_string, utils.dedent(input_string)) + + +class TestListToString(TestCase): + """ + Default function header from utils.py: + list_to_string(inlist, endsep="and", addquote=False) + + Examples: + no endsep: + [1,2,3] -> '1, 2, 3' + with endsep=='and': + [1,2,3] -> '1, 2 and 3' + with addquote and endsep + [1,2,3] -> '"1", "2" and "3"' + """ + + def test_list_to_string(self): + 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], endsep="", addquote=True)) + self.assertEqual('1, 2 and 3', utils.list_to_string([1, 2, 3])) + self.assertEqual('"1", "2" and "3"', utils.list_to_string([1, 2, 3], endsep="and", addquote=True)) + + +class TestMLen(TestCase): + """ + Verifies that m_len behaves like len in all situations except those + where MXP may be involved. + """ + + def test_non_mxp_string(self): + self.assertEqual(utils.m_len('Test_string'), 11) + + def test_mxp_string(self): + self.assertEqual(utils.m_len('|lclook|ltat|le'), 2) + + def test_mxp_ansi_string(self): + self.assertEqual(utils.m_len(ANSIString('|lcl|gook|ltat|le|n')), 2) + + def test_non_mxp_ansi_string(self): + self.assertEqual(utils.m_len(ANSIString('|gHello|n')), 5) + + def test_list(self): + self.assertEqual(utils.m_len([None, None]), 2) + + def test_dict(self): + self.assertEqual(utils.m_len({'hello': True, 'Goodbye': False}), 2) + + +class TestTimeformat(TestCase): + """ + Default function header from utils.py: + time_format(seconds, style=0) + + """ + + def test_style_0(self): + """Test the style 0 of time_format.""" + self.assertEqual(utils.time_format(0, 0), "00:00") + self.assertEqual(utils.time_format(28, 0), "00:00") + self.assertEqual(utils.time_format(92, 0), "00:01") + self.assertEqual(utils.time_format(300, 0), "00:05") + self.assertEqual(utils.time_format(660, 0), "00:11") + self.assertEqual(utils.time_format(3600, 0), "01:00") + self.assertEqual(utils.time_format(3725, 0), "01:02") + self.assertEqual(utils.time_format(86350, 0), "23:59") + self.assertEqual(utils.time_format(86800, 0), "1d 00:06") + self.assertEqual(utils.time_format(130800, 0), "1d 12:20") + self.assertEqual(utils.time_format(530800, 0), "6d 03:26") + + def test_style_1(self): + """Test the style 1 of time_format.""" + self.assertEqual(utils.time_format(0, 1), "0s") + self.assertEqual(utils.time_format(28, 1), "28s") + self.assertEqual(utils.time_format(92, 1), "1m") + self.assertEqual(utils.time_format(300, 1), "5m") + self.assertEqual(utils.time_format(660, 1), "11m") + self.assertEqual(utils.time_format(3600, 1), "1h") + self.assertEqual(utils.time_format(3725, 1), "1h") + self.assertEqual(utils.time_format(86350, 1), "23h") + self.assertEqual(utils.time_format(86800, 1), "1d") + self.assertEqual(utils.time_format(130800, 1), "1d") + self.assertEqual(utils.time_format(530800, 1), "6d") + + def test_style_2(self): + """Test the style 2 of time_format.""" + self.assertEqual(utils.time_format(0, 2), "0 minutes") + self.assertEqual(utils.time_format(28, 2), "0 minutes") + self.assertEqual(utils.time_format(92, 2), "1 minute") + self.assertEqual(utils.time_format(300, 2), "5 minutes") + self.assertEqual(utils.time_format(660, 2), "11 minutes") + self.assertEqual(utils.time_format(3600, 2), "1 hour, 0 minutes") + self.assertEqual(utils.time_format(3725, 2), "1 hour, 2 minutes") + self.assertEqual(utils.time_format(86350, 2), "23 hours, 59 minutes") + self.assertEqual(utils.time_format(86800, 2), + "1 day, 0 hours, 6 minutes") + self.assertEqual(utils.time_format(130800, 2), + "1 day, 12 hours, 20 minutes") + self.assertEqual(utils.time_format(530800, 2), + "6 days, 3 hours, 26 minutes") + + def test_style_3(self): + """Test the style 3 of time_format.""" + self.assertEqual(utils.time_format(0, 3), "") + self.assertEqual(utils.time_format(28, 3), "28 seconds") + self.assertEqual(utils.time_format(92, 3), "1 minute 32 seconds") + self.assertEqual(utils.time_format(300, 3), "5 minutes 0 seconds") + self.assertEqual(utils.time_format(660, 3), "11 minutes 0 seconds") + self.assertEqual(utils.time_format(3600, 3), + "1 hour, 0 minutes") + self.assertEqual(utils.time_format(3725, 3), + "1 hour, 2 minutes 5 seconds") + self.assertEqual(utils.time_format(86350, 3), + "23 hours, 59 minutes 10 seconds") + self.assertEqual(utils.time_format(86800, 3), + "1 day, 0 hours, 6 minutes 40 seconds") + self.assertEqual(utils.time_format(130800, 3), + "1 day, 12 hours, 20 minutes 0 seconds") + self.assertEqual(utils.time_format(530800, 3), + "6 days, 3 hours, 26 minutes 40 seconds") + + def test_style_4(self): + """Test the style 4 of time_format.""" + self.assertEqual(utils.time_format(0, 4), "0 seconds") + self.assertEqual(utils.time_format(28, 4), "28 seconds") + self.assertEqual(utils.time_format(92, 4), "a minute") + self.assertEqual(utils.time_format(300, 4), "5 minutes") + self.assertEqual(utils.time_format(660, 4), "11 minutes") + self.assertEqual(utils.time_format(3600, 4), "an hour") + self.assertEqual(utils.time_format(3725, 4), "an hour") + self.assertEqual(utils.time_format(86350, 4), "23 hours") + self.assertEqual(utils.time_format(86800, 4), "a day") + self.assertEqual(utils.time_format(130800, 4), "a day") + self.assertEqual(utils.time_format(530800, 4), "6 days") + self.assertEqual(utils.time_format(3030800, 4), "a month") + self.assertEqual(utils.time_format(7030800, 4), "2 months") + self.assertEqual(utils.time_format(40030800, 4), "a year") + self.assertEqual(utils.time_format(90030800, 4), "2 years") + + def test_unknown_format(self): + """Test that unknown formats raise exceptions.""" + self.assertRaises(ValueError, utils.time_format, 0, 5) + self.assertRaises(ValueError, utils.time_format, 0, "u")