From 8299f8a6d1f47742ddaf8c90432f93140801f48b Mon Sep 17 00:00:00 2001 From: Molly Draven Date: Thu, 27 Jun 2024 23:54:05 -0400 Subject: [PATCH] refactor: do not redirect stdout / stderr --- .vscode/settings.json | 5 +- .../server/src/server/rusty_motors_server.py | 123 ++++++++++++------ .../server/tests/rusty_motors_server_test.py | 16 ++- 3 files changed, 96 insertions(+), 48 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 2aad72d94..48fb8d84c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { "editor.formatOnSave": true, "editor.codeActionsOnSave": { - "source.organizeImports": "explicit" - } + "source.organizeImports": "explicit" + }, + "python.analysis.typeCheckingMode": "basic" } diff --git a/projects/server/src/server/rusty_motors_server.py b/projects/server/src/server/rusty_motors_server.py index fccde283c..2c4e34c1b 100644 --- a/projects/server/src/server/rusty_motors_server.py +++ b/projects/server/src/server/rusty_motors_server.py @@ -9,15 +9,54 @@ from http.server import ThreadingHTTPServer import sys +root = tk.Tk() + class RustyMotorsServer(tk.Frame): + """ + A class representing the Rusty Motors Server. + + This server provides a graphical user interface (GUI) for controlling and monitoring + various servers used in the Rusty Motors project. + + Args: + master: The master widget. + + Attributes: + args: The parsed server arguments. + http_server: The HTTP server instance. + login_server: The login server instance. + persona_server: The persona server instance. + lobby_server: The lobby server instance. + mcots_server: The MCOTS server instance. + fdMapper: A dictionary mapping file descriptors to server instances. + poller: The poller instance for handling incoming connections. + + Extends: + tk.Frame + """ + def __init__( self, master=None, ): + self.canLog = False + self.args = self.parseServerArguments() - self.initialize_gui_and_servers(master) + try: + self.initialize_gui_and_servers(master) + except Exception as e: + print(e) + sys.exit(1) + + def log(self, message: str, type: str = "info"): + if self.canLog: + self.log_text.insert(tk.END, message, type) + self.log_text.insert(tk.END, "\n", type) + self.log_text.see(tk.END) + else: + print(message) def initialize_gui_and_servers(self, master): self.initialize_gui(master) @@ -29,11 +68,11 @@ def initialize_gui_and_servers(self, master): def initialize_gui(self, master): tk.Frame.__init__(self, master) self.grid() - tk.Wm.title(self.master, "Rusty Motors Server") + root.title("Rusty Motors Server") tk.Label(self, text="Rusty Motors Server").grid(row=0, column=0) tk.Button(self, text="Quit", command=self.shutdown).grid(row=1, column=1) # Create a Label for log messages - self.log = tk.Label(self, text="Log messages will appear here") + self.log_label = tk.Label(self, text="Log messages will appear here") # Create a scrollbar for the log self.scrollbar = tk.Scrollbar(self, orient="vertical") # Create a read-only Text widget to display the log @@ -44,52 +83,49 @@ def initialize_gui(self, master): yscrollcommand=self.scrollbar.set, wrap="word", ) + self.log_text.tag_config("input", foreground="blue") + self.log_text.tag_config("error", foreground="red") # Create an entry widget for user input - self.entry = tk.Entry(self, width=50, invcmd=self.processUserInput) + self.entry = tk.Entry(self, width=50) # Place the widgets on the grid - self.log.grid(row=2, column=0) + self.log_label.grid(row=2, column=0) self.scrollbar.grid(row=3, column=1, sticky="ns") self.log_text.grid(row=3, column=0) self.entry.grid(row=4, column=0) # Configure the scrollbar to scroll the log_text widget self.scrollbar.config(command=self.log_text.yview) - - # Redirect stdout to the log_text widget - self.messageLogger() + self.canLog = True def processUserInput(self): - self.log_text.insert(tk.END, self.entry.get(), "green") - - def messageLogger(self): - class StdoutRedirector: - def __init__(self, text_widget, message_type="stdout"): - self.text_widget = text_widget - - if message_type == "stdout": - self.color = "black" - elif message_type == "stderr": - self.color = "red" - - def write(self, message): - self.text_widget.insert(tk.END, message, self.color) - self.text_widget.see(tk.END) - - sys.stdout = StdoutRedirector(self.log_text, message_type="stdout") - sys.stderr = StdoutRedirector(self.log_text, message_type="stderr") + self.log(f"User input: {self.entry.get()}", "input") + self.entry.delete(0, tk.END) def initializeServers(self): # Setup HTTP server + self.log(f"Starting HTTP server on {self.args.server_address}:{self.args.port}") self.http_server = ThreadingHTTPServer( - (self.args.server_address, self.args.port), WebRequestHandler + (self.args.server_address, int(self.args.port)), WebRequestHandler ) - self.login_server = ThreadingTCPServer(("localhost", 8226), TCPRequestHandler) - self.persona_server = ThreadingTCPServer(("localhost", 8228), TCPRequestHandler) - self.lobby_server = ThreadingTCPServer(("localhost", 7003), TCPRequestHandler) - self.mcots_server = ThreadingTCPServer(("localhost", 43300), TCPRequestHandler) - - self.fdMapper: dict[int, ThreadingHTTPServer | TCPRequestHandler] = { + try: + self.login_server = ThreadingTCPServer( + ("localhost", 8226), TCPRequestHandler + ) + self.persona_server = ThreadingTCPServer( + ("localhost", 8228), TCPRequestHandler + ) + self.lobby_server = ThreadingTCPServer( + ("localhost", 7003), TCPRequestHandler + ) + self.mcots_server = ThreadingTCPServer( + ("localhost", 43300), TCPRequestHandler + ) + except OSError as e: + self.canLog = False + raise Exception(f"Cannot bind to port {e}") + + self.fdMapper: dict[int, ThreadingHTTPServer | ThreadingTCPServer] = { self.http_server.socket.fileno(): self.http_server, self.login_server.fileno(): self.login_server, self.persona_server.fileno(): self.persona_server, @@ -103,7 +139,7 @@ def initializeServers(self): self.poller.register(self.persona_server, POLLIN) self.poller.register(self.lobby_server, POLLIN) self.poller.register(self.mcots_server, POLLIN) - print("Registered all sockets") + self.log("Registered all sockets") def parseServerArguments(self): parser = argparse.ArgumentParser() @@ -134,12 +170,17 @@ def parseServerArguments(self): return args def shutdown(self): - print("Shutting down server") - self.http_server.server_close() - self.login_server.server_close() - self.persona_server.server_close() - self.lobby_server.server_close() - self.mcots_server.server_close() + self.log("Shutting down servers...") + if hasattr(self, "http_server"): + self.http_server.server_close() + if hasattr(self, "login_server"): + self.login_server.server_close() + if hasattr(self, "persona_server"): + self.persona_server.server_close() + if hasattr(self, "lobby_server"): + self.lobby_server.server_close() + if hasattr(self, "mcots_server"): + self.mcots_server.server_close() self.quit() def pollingProcess(self): @@ -157,7 +198,7 @@ def pollingProcess(self): def handle_incoming(self, fd): server = self.fdMapper[fd] - print(f"Handling incoming connection from {server}") + self.log(f"Handling incoming connection from {server}") server.handle_request() def registerKeyboardBinding(self): diff --git a/projects/server/tests/rusty_motors_server_test.py b/projects/server/tests/rusty_motors_server_test.py index 59cb4bff5..9cbdb3397 100644 --- a/projects/server/tests/rusty_motors_server_test.py +++ b/projects/server/tests/rusty_motors_server_test.py @@ -1,4 +1,4 @@ -import sys +import os from tkinter import Text import unittest from unittest.mock import MagicMock, patch @@ -6,7 +6,10 @@ class TestRustyMotorsServer(unittest.TestCase): + def test_shutdown(self): + if "PORT" in os.environ: + del os.environ["PORT"] server = RustyMotorsServer() with patch.object(server, "quit") as mock_quit: server.shutdown() @@ -15,14 +18,17 @@ def test_shutdown(self): def test_messageLogger(self): # Create an instance of RustyMotorsServer + if "PORT" in os.environ: + del os.environ["PORT"] server = RustyMotorsServer() # Create a mock Text widget server.log_text = MagicMock(spec=Text) # Call the messageLogger method with the mock Text widget - server.messageLogger() - # Verify that sys.stdout and sys.stderr are redirected to the mock Text widget - self.assertEqual(sys.stdout.text_widget, server.log_text) - self.assertEqual(sys.stderr.text_widget, server.log_text) + server.log("Hello, World!") + + # Assert that the mock Text widget's insert method was called once + server.log_text.insert.was_called_with(1, "Hello, World!", "black") + server.shutdown()