Use PosixPath arguments with os.remove()
[mudpy.git] / mudpy / tests / selftest.py
index 86ffe22..bb19d33 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2004-2019 mudpy authors. Permission to use, copy,
+# Copyright (c) 2004-2020 mudpy authors. Permission to use, copy,
 # modify, and distribute this software is granted under terms
 # provided in the LICENSE file distributed with this software.
 
 # modify, and distribute this software is granted under terms
 # provided in the LICENSE file distributed with this software.
 
@@ -11,6 +11,15 @@ import sys
 import telnetlib
 import time
 
 import telnetlib
 import time
 
+import yaml
+
+# TODO(fungi) Clean this up once Python 3.5 is no longer supported
+if sys.version < "3.6":
+    import collections
+    odict = collections.OrderedDict
+else:
+    odict = dict
+
 pidfile = "var/mudpy.pid"
 
 test_account0_setup = (
 pidfile = "var/mudpy.pid"
 
 test_account0_setup = (
@@ -143,9 +152,48 @@ test_actor_disappears = (
     (0, r"You suddenly wonder where .* went\.", ""),
 )
 
     (0, r"You suddenly wonder where .* went\.", ""),
 )
 
-test_account1_teardown = (
+test_abort_avatar_deletion = (
     (1, r"What would you like to do\?", "d"),
     (1, r"Whom would you like to delete\?", ""),
     (1, r"What would you like to do\?", "d"),
     (1, r"Whom would you like to delete\?", ""),
+    (1, r"delete an unwanted avatar.*What would you like to do\?", ""),
+)
+
+test_avatar_creation_limit = (
+    (1, r"What would you like to do\?", "c"),
+    (1, "Pick a birth gender for your new avatar:", "m"),
+    (1, "Choose a name for him:", "3"),
+    (1, r"What would you like to do\?", "c"),
+    (1, "Pick a birth gender for your new avatar:", "m"),
+    (1, "Choose a name for him:", "4"),
+    (1, r"What would you like to do\?", "c"),
+    (1, "Pick a birth gender for your new avatar:", "m"),
+    (1, "Choose a name for him:", "7"),
+    (1, r"What would you like to do\?", "c"),
+    (1, "Pick a birth gender for your new avatar:", "m"),
+    (1, "Choose a name for him:", "5"),
+    (1, r"What would you like to do\?", "c"),
+    (1, "Pick a birth gender for your new avatar:", "m"),
+    (1, "Choose a name for him:", "2"),
+    (1, r"What would you like to do\?", "c"),
+    (1, "Pick a birth gender for your new avatar:", "m"),
+    (1, "Choose a name for him:", "6"),
+    (1, r"What would you like to do\?", "c"),
+    (1, r"That is not a valid choice\.\.\.", ""),
+)
+
+test_avatar_deletion = (
+    (1, r"What would you like to do\?", "d"),
+    (1, r"Whom would you like to delete\?", "1"),
+    (1, r"create a new avatar.*What would you like to do\?", ""),
+)
+
+test_abort_account_deletion = (
+    (1, r"What would you like to do\?", "p"),
+    (1, r"permanently delete your account\?", ""),
+    (1, r"What would you like to do\?", ""),
+)
+
+test_account_deletion = (
     (1, r"What would you like to do\?", "p"),
     (1, r"permanently delete your account\?", "y"),
     (1, r"Disconnecting\.\.\.", ""),
     (1, r"What would you like to do\?", "p"),
     (1, r"permanently delete your account\?", "y"),
     (1, r"Disconnecting\.\.\.", ""),
@@ -292,30 +340,45 @@ test_show_element = (
         r'  \x1b\[32mgender: \x1b\[31mfemale.*> ', ""),
 )
 
         r'  \x1b\[32mgender: \x1b\[31mfemale.*> ', ""),
 )
 
-test_show_result = (
-    (2, "> ", "show result 12345*67890"),
-    (2, r"\r\n838102050\r\n.*> ", "show result 1/0"),
-    (2, r"Your expression raised an exception.*division by zero.*> ",
-     "show result mudpy"),
-    (2, r"<module 'mudpy' from .*> ", "show result re"),
-    (2, r"Your expression raised an exception.*name 're' is not defined.*> ",
-     "show result universe"),
-    (2, r"<mudpy\.misc\.Universe object at 0x.*> ", "show result actor"),
-    (2, r"Your expression raised an exception.*name 'actor' is not "
-        r"defined.*> ", ""),
+test_evaluate = (
+    (2, "> ", "evaluate 12345*67890"),
+    (2, r"\r\n838102050\r\n.*> ", "evaluate 1/0"),
+    (2, "Your expression raised an exception.*division by zero.*> ",
+     "evaluate mudpy"),
+    (2, "<module 'mudpy' from.*> ", "evaluate re"),
+    (2, "Your expression raised an exception.*name 're' is not defined.*> ",
+     "evaluate universe"),
+    (2, r"<mudpy\.misc\.Universe object at 0x.*> ", "evaluate actor"),
+    (2, "Your expression raised an exception.*name 'actor' is not defined.*> ",
+        "evaluate dir(mudpy)"),
+    (2, "__builtins__.*> ", "evaluate mudpy.__builtins__.open"),
+    (2, "not allowed.*> ", "evaluate (lambda x: x + 1)(2)"),
+    (2, "not allowed.*> ", ""),
+)
+
+test_debug_restricted = (
+    (0, "> ", "help evaluate"),
+    (0, r"That is not an available command\.", "evaluate"),
+    (0, '(not sure what "evaluate" means|Arglebargle, glop-glyf)', ""),
+)
+
+test_debug_disabled = (
+    (2, "> ", "help evaluate"),
+    (2, r"That is not an available command\.", "evaluate"),
+    (2, '(not sure what "evaluate" means|Arglebargle, glop-glyf)', ""),
 )
 
 test_show_log = (
     (2, "> ", "show log"),
     (2, r"There are [0-9]+ log lines in memory and [0-9]+ at or above level "
 )
 
 test_show_log = (
     (2, "> ", "show log"),
     (2, r"There are [0-9]+ log lines in memory and [0-9]+ at or above level "
-        r"[0-9]+\. The matching lines\r\nfrom [0-9]+ to [0-9]+ are:", ""),
+        r"[0-9]+\. The matching.*from [0-9]+ to [0-9]+ are:", ""),
 )
 
 test_custom_loglevel = (
     (2, "> ", "set account.admin loglevel 2"),
     (2, "You have successfully .*> ", "show log"),
     (2, r"There are [0-9]+ log lines in memory and [0-9]+ at or above level "
 )
 
 test_custom_loglevel = (
     (2, "> ", "set account.admin loglevel 2"),
     (2, "You have successfully .*> ", "show log"),
     (2, r"There are [0-9]+ log lines in memory and [0-9]+ at or above level "
-        r"[0-9]+\. The matching lines\r\nfrom [0-9]+ to [0-9]+ are:", ""),
+        r"[0-9]+\. The matching.*from [0-9]+ to [0-9]+ are:", ""),
 )
 
 test_invalid_loglevel = (
 )
 
 test_invalid_loglevel = (
@@ -330,20 +393,16 @@ test_log_no_errors = (
 
 final_cleanup = (
     (0, "> ", "quit"),
 
 final_cleanup = (
     (0, "> ", "quit"),
-    (0, r"What would you like to do\?", "d"),
-    (0, r"Whom would you like to delete\?", ""),
     (0, r"What would you like to do\?", "p"),
     (0, r"permanently delete your account\?", "y"),
     (0, r"Disconnecting\.\.\.", ""),
     (2, "> ", "quit"),
     (0, r"What would you like to do\?", "p"),
     (0, r"permanently delete your account\?", "y"),
     (0, r"Disconnecting\.\.\.", ""),
     (2, "> ", "quit"),
-    (2, r"What would you like to do\?", "d"),
-    (2, r"Whom would you like to delete\?", ""),
     (2, r"What would you like to do\?", "p"),
     (2, r"permanently delete your account\?", "y"),
     (2, r"Disconnecting\.\.\.", ""),
 )
 
     (2, r"What would you like to do\?", "p"),
     (2, r"permanently delete your account\?", "y"),
     (2, r"Disconnecting\.\.\.", ""),
 )
 
-dialogue = (
+dialogue = odict((
     (test_account0_setup, "first account setup"),
     (test_account1_setup, "second account setup"),
     (test_actor_appears, "actor spontaneous appearance"),
     (test_account0_setup, "first account setup"),
     (test_account1_setup, "second account setup"),
     (test_actor_appears, "actor spontaneous appearance"),
@@ -357,7 +416,11 @@ dialogue = (
     (test_escape_macros, "replacement macros are escaped"),
     (test_movement, "movement"),
     (test_actor_disappears, "actor spontaneous disappearance"),
     (test_escape_macros, "replacement macros are escaped"),
     (test_movement, "movement"),
     (test_actor_disappears, "actor spontaneous disappearance"),
-    (test_account1_teardown, "second account teardown"),
+    (test_abort_avatar_deletion, "abort avatar deletion"),
+    (test_avatar_creation_limit, "avatar creation limit"),
+    (test_avatar_deletion, "avatar deletion"),
+    (test_abort_account_deletion, "abort account deletion"),
+    (test_account_deletion, "account deletion"),
     (test_admin_setup, "admin account setup"),
     (test_preferences, "set and show preferences"),
     (test_crlf_eol, "send crlf from the client as eol"),
     (test_admin_setup, "admin account setup"),
     (test_preferences, "set and show preferences"),
     (test_crlf_eol, "send crlf from the client as eol"),
@@ -378,20 +441,30 @@ dialogue = (
     (test_show_groups, "show groups"),
     (test_show_group, "show group"),
     (test_show_element, "show element"),
     (test_show_groups, "show groups"),
     (test_show_group, "show group"),
     (test_show_element, "show element"),
-    (test_show_result, "show result of a python expression"),
+    (test_evaluate, "show results of python expressions"),
+    (test_debug_restricted, "only admins can run debug commands"),
+    (test_debug_disabled, "debugging commands only in debug mode"),
     (test_show_log, "show log"),
     (test_custom_loglevel, "custom loglevel"),
     (test_invalid_loglevel, "invalid loglevel"),
     (test_log_no_errors, "no errors logged"),
     (final_cleanup, "delete remaining accounts"),
     (test_show_log, "show log"),
     (test_custom_loglevel, "custom loglevel"),
     (test_invalid_loglevel, "invalid loglevel"),
     (test_log_no_errors, "no errors logged"),
     (final_cleanup, "delete remaining accounts"),
+))
+
+debug_tests = (
+    test_evaluate,
+)
+
+nondebug_tests = (
+    test_debug_disabled,
 )
 
 
 def start_service(config):
     # Clean up any previously run daemon which didn't terminate
     if os.path.exists(pidfile):
 )
 
 
 def start_service(config):
     # Clean up any previously run daemon which didn't terminate
     if os.path.exists(pidfile):
-        pidfd = open(pidfile)
-        pid = int(pidfd.read())
+        with open(pidfile) as pidfd:
+            pid = int(pidfd.read())
         try:
             # Stop the running service
             os.kill(pid, 15)
         try:
             # Stop the running service
             os.kill(pid, 15)
@@ -404,9 +477,7 @@ def start_service(config):
 
     # Clean up any previous test output
     for f in pathlib.Path(".").glob("capture_*.log"):
 
     # Clean up any previous test output
     for f in pathlib.Path(".").glob("capture_*.log"):
-        # have to use .name here since remove() doesn't support passing a
-        # PosixPath argument until Python3.6
-        os.remove(f.name)
+        os.remove(f)
     for d in ("data", "var"):
         shutil.rmtree(d, ignore_errors=True)
 
     for d in ("data", "var"):
         shutil.rmtree(d, ignore_errors=True)
 
@@ -415,7 +486,7 @@ def start_service(config):
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE)
     time.sleep(1)
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE)
     time.sleep(1)
-    return(service)
+    return service
 
 
 def stop_service(service):
 
 
 def stop_service(service):
@@ -423,7 +494,7 @@ def stop_service(service):
 
     # The no-op case when no service was started
     if service is None:
 
     # The no-op case when no service was started
     if service is None:
-        return(success)
+        return success
 
     # This handles when the service is running as a direct child process
     service.terminate()
 
     # This handles when the service is running as a direct child process
     service.terminate()
@@ -434,8 +505,8 @@ def stop_service(service):
 
     # This cleans up a daemonized and disassociated service
     if os.path.exists(pidfile):
 
     # This cleans up a daemonized and disassociated service
     if os.path.exists(pidfile):
-        pidfd = open(pidfile)
-        pid = int(pidfd.read())
+        with open(pidfile) as pidfd:
+            pid = int(pidfd.read())
         try:
             # Stop the running service
             os.kill(pid, 15)
         try:
             # Stop the running service
             os.kill(pid, 15)
@@ -451,13 +522,13 @@ def stop_service(service):
     # Log the contents of stdout and stderr, if any
     stdout, stderr = service.communicate()
     tlog("\nRecording stdout as capture_stdout.log.")
     # Log the contents of stdout and stderr, if any
     stdout, stderr = service.communicate()
     tlog("\nRecording stdout as capture_stdout.log.")
-    serviceout = open("capture_stdout.log", "w")
-    serviceout.write(stdout.decode("utf-8"))
+    with open("capture_stdout.log", "w") as serviceout:
+        serviceout.write(stdout.decode("utf-8"))
     tlog("\nRecording stderr as capture_stderr.log.")
     tlog("\nRecording stderr as capture_stderr.log.")
-    serviceerr = open("capture_stderr.log", "w")
-    serviceerr.write(stderr.decode("utf-8"))
+    with open("capture_stderr.log", "w") as serviceerr:
+        serviceerr.write(stderr.decode("utf-8"))
 
 
-    return(success)
+    return success
 
 
 def tlog(message, quiet=False):
 
 
 def tlog(message, quiet=False):
@@ -480,6 +551,13 @@ def option_callback(telnet_socket, command, option):
         telnet_socket.send(telnetlib.IAC + telnetlib.DONT + option)
 
 
         telnet_socket.send(telnetlib.IAC + telnetlib.DONT + option)
 
 
+def check_debug():
+    if len(sys.argv) > 1:
+        config = yaml.safe_load(open(sys.argv[1]))
+        return config.get(".mudpy.limit.debug", False)
+    return False
+
+
 def main():
     captures = ["", "", ""]
     lusers = [telnetlib.Telnet(), telnetlib.Telnet(), telnetlib.Telnet()]
 def main():
     captures = ["", "", ""]
     lusers = [telnetlib.Telnet(), telnetlib.Telnet(), telnetlib.Telnet()]
@@ -489,10 +567,20 @@ def main():
     if len(sys.argv) > 1:
         # Start the service if a config file was provided on the command line
         service = start_service(sys.argv[1])
     if len(sys.argv) > 1:
         # Start the service if a config file was provided on the command line
         service = start_service(sys.argv[1])
+        if not service:
+            tlog("\nERROR: Service did not start.\n")
+            sys.exit(1)
     for luser in lusers:
         luser.open("::1", 4000)
         luser.set_option_negotiation_callback(option_callback)
     for luser in lusers:
         luser.open("::1", 4000)
         luser.set_option_negotiation_callback(option_callback)
-    for test, description in dialogue:
+    selected_dialogue = odict(dialogue)
+    if check_debug():
+        for test in nondebug_tests:
+            del selected_dialogue[test]
+    else:
+        for test in debug_tests:
+            del selected_dialogue[test]
+    for test, description in selected_dialogue.items():
         tlog("\nTesting %s..." % description)
         test_start = time.time()
         for conversant, question, answer in test:
         tlog("\nTesting %s..." % description)
         test_start = time.time()
         for conversant, question, answer in test: