Retry client connections in selftest
[mudpy.git] / mudpy / tests / selftest.py
index c14baff..c0bfc3c 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2004-2020 mudpy authors. Permission to use, copy,
+# Copyright (c) 2004-2021 mudpy authors. Permission to use, copy,
 # modify, and distribute this software is granted under terms
 # provided in the LICENSE file distributed with this software.
 
@@ -461,7 +461,6 @@ def start_service(config):
         try:
             # Stop the running service
             os.kill(pid, 15)
-            time.sleep(1)
         except ProcessLookupError:
             # If there was no process, just remove the stale PID file
             os.remove(pidfile)
@@ -478,7 +477,6 @@ def start_service(config):
     service = subprocess.Popen(("mudpy", config),
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE)
-    time.sleep(1)
     return service
 
 
@@ -546,11 +544,37 @@ def option_callback(telnet_socket, command, option):
 
 def check_debug():
     if len(sys.argv) > 1:
-        config = yaml.safe_load(open(sys.argv[1]))
+        with open(sys.argv[1]) as config_fd:
+            config = yaml.safe_load(config_fd)
         return config.get(".mudpy.limit.debug", False)
     return False
 
 
+def connect_client(luser, service):
+    # Try multiple times to connect, with an exponential backoff
+    for retry in range(5):
+        try:
+            # Skipping the retry=0 case gives an immediate first attempt
+            if retry:
+                time.sleep((2 ** retry) / 10)
+            luser.open("::1", 4000)
+            # Attempt to poll the connection, closing if unusable
+            try:
+                luser.fill_rawq()
+            except ConnectionResetError:
+                luser.close()
+                continue
+            # Short-circuit if we get this far, connection is safe to use
+            return luser
+        except ConnectionRefusedError:
+            continue
+    else:
+        # Connection retries have been exhausted, so give up
+        tlog("\nERROR: Client could not connect.\n")
+        stop_service(service)
+        sys.exit(1)
+
+
 def main():
     captures = ["", "", ""]
     lusers = [telnetlib.Telnet(), telnetlib.Telnet(), telnetlib.Telnet()]
@@ -564,7 +588,7 @@ def main():
             tlog("\nERROR: Service did not start.\n")
             sys.exit(1)
     for luser in lusers:
-        luser.open("::1", 4000)
+        connect_client(luser, service)
         luser.set_option_negotiation_callback(option_callback)
     selected_dialogue = dict(dialogue)
     if check_debug():
@@ -583,11 +607,7 @@ def main():
                 index, match, received = lusers[conversant].expect(
                     [re.compile(question.encode("utf-8"), flags=re.DOTALL)], 5)
                 captures[conversant] += received.decode("utf-8")
-            except ConnectionResetError:
-                tlog("\nERROR: Unable to connect to server.")
-                success = False
-                break
-            except EOFError:
+            except (ConnectionResetError, EOFError):
                 tlog("\nERROR: luser%s premature disconnection expecting:\n\n"
                      "%s\n\n"
                      "Check the end of capture_%s.log for received data."