├── showcase.png ├── .gitignore ├── LICENSE ├── UDPMessages.py ├── UDPTranslator.py ├── README.md └── testo.py /showcase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vlad6422/VUT_IPK_CLIENT_TESTS/HEAD/showcase.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ignore everything, but the testing_server.py file 2 | * 3 | !testing_server.py 4 | !testo.py 5 | !README.md 6 | !LICENSE 7 | !.gitignore 8 | !*.png 9 | !UDPTranslator.py 10 | !UDPMessages.py 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 xhobza03 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /UDPMessages.py: -------------------------------------------------------------------------------- 1 | def confirm(ref_message_id): 2 | return bytes([0x00]) + ref_message_id 3 | 4 | def reply(message_id, result, ref_message_id, message_contents): 5 | message_id_bytes = message_id.to_bytes(2, 'big') 6 | result_byte = bytes([result]) 7 | message_contents_bytes = message_contents.encode() + b'\x00' 8 | return bytes([0x01]) + message_id_bytes + result_byte + ref_message_id + message_contents_bytes 9 | 10 | def auth(message_id, username, display_name, secret): 11 | message_id_bytes = message_id.to_bytes(2, 'big') 12 | username_bytes = username.encode() + b'\x00' 13 | display_name_bytes = display_name.encode() + b'\x00' 14 | secret_bytes = secret.encode() + b'\x00' 15 | return bytes([0x02]) + message_id_bytes + username_bytes + display_name_bytes + secret_bytes 16 | 17 | def join(message_id, channel_id, display_name): 18 | message_id_bytes = message_id.to_bytes(2, 'big') 19 | channel_id_bytes = channel_id.encode() + b'\x00' 20 | display_name_bytes = display_name.encode() + b'\x00' 21 | return bytes([0x03]) + message_id_bytes + channel_id_bytes + display_name_bytes 22 | 23 | def msg(message_id, display_name, message_contents): 24 | message_id_bytes = message_id.to_bytes(2, 'big') 25 | display_name_bytes = display_name.encode() + b'\x00' 26 | message_contents_bytes = message_contents.encode() + b'\x00' 27 | return bytes([0x04]) + message_id_bytes + display_name_bytes + message_contents_bytes 28 | 29 | def err(message_id, display_name, message_contents): 30 | message_id_bytes = message_id.to_bytes(2, 'big') 31 | display_name_bytes = display_name.encode() + b'\x00' 32 | message_contents_bytes = message_contents.encode() + b'\x00' 33 | return bytes([0xFE]) + message_id_bytes + display_name_bytes + message_contents_bytes 34 | 35 | def bye(message_id, display_name): 36 | display_name_bytes = display_name.encode() + b'\x00' 37 | return bytes([0xFF]) + message_id.to_bytes(2, 'big') + display_name_bytes 38 | 39 | def ping(message_id): 40 | return bytes([0xFD]) + message_id.to_bytes(2, 'big') 41 | -------------------------------------------------------------------------------- /UDPTranslator.py: -------------------------------------------------------------------------------- 1 | MESSAGE_TYPES = { 2 | 0x00: "CONFIRM", 3 | 0x01: "REPLY", 4 | 0x02: "AUTH", 5 | 0x03: "JOIN", 6 | 0x04: "MSG", 7 | 0xFE: "ERR", 8 | 0xFF: "BYE", 9 | } 10 | 11 | 12 | def getMessageId(data): 13 | pointer = 1 # Start after the message type byte 14 | message_id, pointer = read_bytes(data, pointer, 2) 15 | return message_id 16 | 17 | 18 | def read_byte(data, pointer): 19 | byte = data[pointer] 20 | pointer += 1 21 | return byte, pointer 22 | 23 | 24 | def read_bytes(data, pointer, num_bytes): 25 | bytes_read = data[pointer : pointer + num_bytes] 26 | pointer += num_bytes 27 | return bytes_read, pointer 28 | 29 | 30 | def read_variable_length_string(data, pointer): 31 | string = b"" 32 | while True: 33 | byte, pointer = read_byte(data, pointer) 34 | if byte == 0: 35 | break 36 | string += bytes([byte]) 37 | return string.decode(), pointer 38 | 39 | 40 | def translateMessage(data): 41 | pointer = 0 42 | message_type, pointer = read_byte(data, pointer) 43 | if message_type not in MESSAGE_TYPES: 44 | return "Unknown message type" 45 | 46 | message_id, pointer = read_bytes(data, pointer, 2) 47 | 48 | if message_type == 0x00: # CONFIRM 49 | ref_message_id, pointer = read_bytes(data, pointer, 2) 50 | return f'REPLY IS {int.from_bytes(ref_message_id, byteorder="big")}\r\n' 51 | 52 | elif message_type == 0x01: # REPLY 53 | result, pointer = read_byte(data, pointer) 54 | ref_message_id, pointer = read_bytes(data, pointer, 2) 55 | message_contents, pointer = read_variable_length_string(data, pointer) 56 | return f'REPLY {"OK" if result == 1 else "NOK"} IS {int.from_bytes(ref_message_id, byteorder="big")} AS {message_contents}\r\n' 57 | 58 | elif message_type == 0x02: # AUTH 59 | username, pointer = read_variable_length_string(data, pointer) 60 | display_name, pointer = read_variable_length_string(data, pointer) 61 | secret, pointer = read_variable_length_string(data, pointer) 62 | return f"AUTH IS {username} AS {display_name} USING {secret}\r\n" 63 | 64 | elif message_type == 0x03: # JOIN 65 | channel_id, pointer = read_variable_length_string(data, pointer) 66 | display_name, pointer = read_variable_length_string(data, pointer) 67 | return f"JOIN IS {channel_id} AS {display_name}\r\n" 68 | 69 | elif message_type == 0x04: # MSG 70 | display_name, pointer = read_variable_length_string(data, pointer) 71 | message_contents, pointer = read_variable_length_string(data, pointer) 72 | return f"MSG FROM {display_name} IS {message_contents}\r\n" 73 | 74 | elif message_type == 0xFE: # ERR 75 | display_name, pointer = read_variable_length_string(data, pointer) 76 | message_contents, pointer = read_variable_length_string(data, pointer) 77 | return f"ERR FROM {display_name} IS {message_contents}\r\n" 78 | 79 | elif message_type == 0xFF: # BYE 80 | display_name, pointer = read_variable_length_string(data, pointer) 81 | return f"BYE FROM {display_name}\r\n" 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IPK-01 Testing Server 2 | 3 | Author: **Tomáš Hobza** ([xhobza03](mailto:xhobza03@vutbr.cz)) @ FIT VUT 2024 4 | 5 | Co-Author IPK25: **Malashchuk Vladyslav** @ FIT VUT 2025 6 | 7 | # Warning 8 | 9 | I updated IPK24->IPK25 and passed all tests. But made it in 30 min, so it can have troubles. 10 | 11 | I have : ✅ 58/58 test cases passed 12 | 13 | **Since it is your application, you should also be the author of test sets (i.e., avoid using someone else's tests).** 14 | 15 | If you add them to the documentation, do not try to pass them off as your own. Clearly indicate that you are not the author, add a quote, indicate that the tests are not the main ones and are considered only as part of self-checking and do not indicate the correct operation of the application. These tests cannot be part of your testing! You can indicate that you tried them for self-checking and do not consider them part of testing. 16 | 17 | Use these tests, but do not expect that even passing them will give you the maximum score during the assessment. 18 | 19 | These tests should not be considered in any case as the only ones, you can test at your own discretion using these tests. They do not cover all possible options! They do not check for exact compliance with the rules. The test mainly checks the basic things. You can only count on the fact that all tests will pass, your application meets the basic functionality, but this does not mean that when assessing the project you will receive the maximum score or all the tests will pass there. 20 | 21 | These tests are openly available, they are not a reference for writing your own tests and writing your project. They can only be used as an additional validation / check of the project before submitting your project. 22 | 23 | All tests described here are fiction of the author and are not real. Any coincidence with reality is a myth. 24 | # If you want to include test results in your IPK documentation made by tests from this repository. I advise you to include clearly indicate that you are not the author and leave a link to this git repository. 25 | 26 | Example: 27 | 28 | ... 29 | 30 | testy [4] 31 | 32 | ... 33 | 34 | [4] MALASHCHUK Vladyslav, Tomáš HOBZA, et al. VUT_IPK_CLIENT_TESTS [online]. GitHub, 2025 [cit. 2025-04-16]. Available at: https://github.com/Vlad6422/VUT_IPK_CLIENT_TESTS 35 | 36 | ## 📚 Info 37 | 38 | Testing server for testing the TCP/UDP messaging client for the IPK project 2025 written in Python. Feel free to use it and modify it however you want. 39 | 40 | > ⚠️ I am not an expert in communications nor Python so go on with caution. Any problems that you might find you can hit me up (email/discord) or create a PR. 41 | 42 | ## 🔄 Usage 43 | 44 | The specific usage can be listed with the `-h` flag, but here's probably the most important stuff: 45 | 46 | - `` - provide the path to the client executable 47 | - `-p ` - choose the tested protocol (default is both) 48 | - `-d` - will show both `stdout` and `stdin` of the running client 49 | - `-t ` - run a specific test case only 50 | 51 | ## Run example (For lazy) 52 | - `git clone https://github.com/Vlad6422/VUT_IPK_CLIENT_TESTS.git` 53 | - `cd VUT_IPK_CLIENT_TESTS/` 54 | - `pip3 install termcolor` 55 | - `python3 testo.py ./ipk25chat-client` <- `ipk25chat-client` is your binary file in same directory with testo.py, or path to binary. 56 | 57 | ## 🛠️ Prerequisites 58 | 59 | You might need to install `termcolor` if you don't have it yet using `pip3 install termcolor`. 60 | 61 | ## ⚖️ License 62 | 63 | See [LICENSE](LICENSE). 64 | 65 | ![showcase of how the tests look](showcase.png) 66 | -------------------------------------------------------------------------------- /testo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import subprocess 4 | import sys 5 | import threading 6 | import queue 7 | import math 8 | from os import get_terminal_size 9 | from termcolor import colored, cprint 10 | from time import sleep 11 | from sys import platform 12 | import signal 13 | import socket 14 | import select 15 | import re 16 | import pty 17 | import os 18 | 19 | from UDPTranslator import translateMessage, getMessageId 20 | from UDPMessages import confirm, reply, auth, join, msg, err, bye, ping 21 | 22 | global debug 23 | global run_tcp 24 | global run_udp 25 | global test 26 | 27 | # Define a global list to hold all test case functions 28 | udp_test_cases = [] 29 | tcp_test_cases = [] 30 | 31 | 32 | class TimeoutError(Exception): 33 | def __init__(self, message): 34 | self.message = message 35 | super().__init__(self.message) 36 | 37 | 38 | def testcase(func): 39 | def wrapper(tester, *args, **kwargs): 40 | passed = False 41 | 42 | title = f" ⏳ Starting test '{func.__name__}' " 43 | start_sep = "=" * math.floor((get_terminal_size().columns - len(title) - 1) / 2) 44 | end_sep = "=" * ( 45 | get_terminal_size().columns - (len(start_sep) + len(title)) - 1 46 | ) 47 | print(colored("\n" + start_sep + title + end_sep, "yellow")) 48 | try: 49 | func(tester, *args, **kwargs) 50 | print(colored(f"✅ Test '{func.__name__}': PASSED", "green")) 51 | passed = True 52 | except AssertionError as e: 53 | print(colored(f"❌ Test '{func.__name__}': FAILED - {e}", "red")) 54 | tester.dump() 55 | except TimeoutError as e: 56 | print(colored(f"❌ Test '{func.__name__}': FAILED - Timeout - {e}", "red")) 57 | tester.dump() 58 | except Exception as e: 59 | print(colored(f"❌ Test '{func.__name__}': ERROR - {e}", "red")) 60 | print(colored(f"Test '{func.__name__}' finished", "yellow")) 61 | tester.teardown() # Clean up after test 62 | 63 | return passed 64 | 65 | # pass in the testcase name 66 | wrapper_func = wrapper 67 | wrapper_func.__name__ = func.__name__ 68 | 69 | # Register the test case 70 | if "udp" in func.__name__: 71 | udp_test_cases.append(wrapper_func) 72 | elif "tcp" in func.__name__: 73 | tcp_test_cases.append(wrapper_func) 74 | return wrapper 75 | 76 | 77 | class ExecutableTester: 78 | def __init__(self, executable_path): 79 | self.executable_path = executable_path 80 | self.process = None 81 | self.stdout_queue = queue.Queue() 82 | self.stderr_queue = queue.Queue() 83 | self.return_code = None 84 | self.server_socket = None 85 | self.connection_socket = None # For TCP connections 86 | self.client_address = None # For UDP responses 87 | self.send_confirm = True 88 | self.history = "" 89 | self.accept_thread = None # Keep track of the thread accepting connections 90 | 91 | def start_server(self, protocol, port): 92 | if protocol.lower() == "tcp": 93 | self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 94 | self.server_socket.setsockopt( 95 | socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 96 | ) # Set SO_REUSEADDR 97 | self.server_socket.bind(("localhost", port)) 98 | self.server_socket.listen(1) 99 | # Start a new thread to run the blocking accept call 100 | thread = threading.Thread(target=self.accept_connection) 101 | thread.daemon = True # Daemon threads exit when the main program does 102 | thread.start() 103 | 104 | elif protocol.lower() == "udp": 105 | self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 106 | self.server_socket.bind(("localhost", port)) 107 | 108 | def accept_connection(self): 109 | self.connection_socket, _ = self.server_socket.accept() 110 | 111 | def stop_server(self): 112 | if self.connection_socket: 113 | self.connection_socket.close() 114 | if self.server_socket: 115 | self.server_socket.close() 116 | if self.accept_thread: # Ensure the accept thread has finished 117 | self.accept_thread.join() 118 | 119 | def send_message(self, message): 120 | if self.connection_socket: # TCP 121 | self.connection_socket.sendall(message.encode()) 122 | elif self.server_socket and self.client_address: # UDP 123 | self.server_socket.sendto(message, self.client_address) 124 | 125 | def receive_message(self, timeout=5): 126 | if self.server_socket: 127 | self.server_socket.settimeout(timeout) 128 | try: 129 | if self.connection_socket: # TCP 130 | return self.connection_socket.recv(1024).decode() 131 | else: # UDP 132 | message, self.client_address = self.server_socket.recvfrom(1024) 133 | if message[0] != 0: 134 | # we should confirm it directly 135 | if self.send_confirm: 136 | self.confirm(message) 137 | return message 138 | except socket.timeout: 139 | raise TimeoutError("Socket timed out.") 140 | 141 | def setup(self, args=["-t", "udp", "-s", "localhost", "-p", "4567"]): 142 | if self.process: 143 | self.teardown() 144 | self.stdout_queue = queue.Queue() 145 | self.stderr_queue = queue.Queue() 146 | 147 | if platform == "linux" or platform == "linux2": 148 | 149 | self.process = subprocess.Popen( 150 | ["stdbuf", "-o0", self.executable_path] + args, 151 | stdin=subprocess.PIPE, 152 | stdout=subprocess.PIPE, 153 | stderr=subprocess.PIPE, 154 | text=True, 155 | ) 156 | 157 | self._start_thread(self.read_stdout, self.stdout_queue) 158 | self._start_thread(self.read_stderr, self.stderr_queue) 159 | elif platform == "darwin": 160 | master, slave = pty.openpty() # Open a pseudo-terminal pair 161 | 162 | self.process = subprocess.Popen( 163 | [self.executable_path] + args, 164 | stdin=subprocess.PIPE, 165 | stdout=slave, 166 | stderr=subprocess.PIPE, 167 | text=True, 168 | bufsize=0, # Set small buffer size 169 | ) 170 | 171 | os.close(slave) # Close the slave fd, the subprocess will write to it 172 | self.stdout_fd = master # Use the master for reading output 173 | 174 | self._start_thread(self.buffer_stdout_fd, self.stdout_queue) 175 | self._start_thread(self.read_stdout, self.stdout_queue) 176 | 177 | self.return_code = None 178 | 179 | sleep(0.2) # Give some time for the process to start 180 | 181 | def _start_thread(self, target, queue): 182 | thread = threading.Thread(target=target, args=(queue,)) 183 | thread.daemon = True # Thread dies with the program 184 | thread.start() 185 | 186 | def buffer_stdout_fd(self, queue): 187 | while True: 188 | if self.stdout_fd and self.process and self.process.poll() is not None: 189 | break # Subprocess has terminated, exit the loop 190 | try: 191 | line = os.read(self.stdout_fd, 1024).decode("utf-8") 192 | if line: 193 | if debug: 194 | print( 195 | colored("STDOUT:", "blue") + colored(line, "blue"), end="" 196 | ) 197 | queue.put(line) 198 | except Exception as e: 199 | print(f"Error reading from pty: {e}") 200 | break 201 | 202 | def read_stdout(self, queue): 203 | for line in iter(self.process.stdout.readline, ""): 204 | if debug: 205 | print(colored("STDOUT:", "blue"), colored(line, "blue"), end="") 206 | queue.put(line) 207 | 208 | def read_stderr(self, queue): 209 | for line in iter(self.process.stderr.readline, ""): 210 | if debug: 211 | print(colored("stderr:", "magenta"), colored(line, "magenta"), end="") 212 | queue.put(line) 213 | 214 | def execute(self, input_data): 215 | self.history += "stdin:" + input_data + "\n" 216 | self.process.stdin.write(input_data + "\n") 217 | self.process.stdin.flush() 218 | 219 | sleep(0.2) 220 | 221 | def get_stdout(self): 222 | output = [] 223 | while not self.stdout_queue.empty(): 224 | output.append(self.stdout_queue.get()) 225 | self.history += "stdout:".join(output) 226 | return "".join(output) 227 | 228 | def get_stderr(self): 229 | output = [] 230 | while not self.stderr_queue.empty(): 231 | output.append(self.stderr_queue.get()) 232 | return "".join(output) 233 | 234 | def teardown(self): 235 | if self.process: 236 | self.process.terminate() 237 | self.process.wait() 238 | self.return_code = self.process.returncode 239 | self.process = None 240 | 241 | self.stop_server() 242 | self.server_socket = None 243 | self.connection_socket = None 244 | 245 | def confirm(self, message): 246 | self.send_message(b"\x00" + getMessageId(message)) 247 | 248 | def get_return_code(self): 249 | return self.return_code 250 | 251 | def send_signal(self, signal): 252 | self.process.send_signal(signal) 253 | 254 | def send_eof(self): 255 | self.process.stdin.close() 256 | 257 | def dump(self): 258 | 259 | print(colored("stdout now", "magenta")) 260 | print(self.get_stdout()) 261 | 262 | print(colored("stdout", "magenta")) 263 | print(self.get_stdout()) 264 | 265 | print(colored("stderr", "magenta")) 266 | print(self.get_stderr()) 267 | 268 | print(colored("History", "magenta")) 269 | print(self.history) 270 | 271 | def setClientAddress(self, client_address): 272 | self.client_address = client_address 273 | 274 | 275 | ### TEST CASES ### 276 | 277 | 278 | # PART 1 - Testing command-line aguments 279 | 280 | 281 | @testcase 282 | def no_args(tester): 283 | """Test that the program exits with a non-zero exit code when no arguments are provided""" 284 | tester.setup(args=[]) 285 | assert tester.get_return_code() != 0, "Expected non-zero exit code." 286 | 287 | 288 | @testcase 289 | def no_type_arg(tester): 290 | """Test that the program exits with a non-zero exit code when the -t argument is not provided.""" 291 | tester.setup(args=["-s", "localhost"]) 292 | assert tester.get_return_code() != 0, "Expected non-zero exit code." 293 | 294 | 295 | @testcase 296 | def no_hostname(tester): 297 | """Test that the program exits with a non-zero exit code when the -s argument is not provided.""" 298 | tester.setup(args=["-t", "udp"]) 299 | assert tester.get_return_code() != 0, "Expected non-zero exit code." 300 | 301 | 302 | @testcase 303 | def all_args(tester): 304 | """Test that the program exits with a non-zero exit code when the -s argument is not provided.""" 305 | tester.setup(args=["-t", "udp", "-s", "localhost", "-p", "4567"]) 306 | tester.send_eof() 307 | assert tester.get_return_code() == None, "Expected zero exit code." 308 | 309 | 310 | # PART 2: UDP 311 | 312 | @testcase 313 | def udp_help_command(tester): 314 | """Test that the /help command shows some output.""" 315 | auth_and_reply(tester) 316 | tester.execute("/help") 317 | sleep(0.2) 318 | stdout = tester.get_stdout() 319 | # Check for any output after the command 320 | assert len(stdout.strip()) > 0, "Expected some output for /help command." 321 | 322 | @testcase 323 | def udp_hello(tester): 324 | """Test that the program does not accept any message commands before the user is authenticated.""" 325 | tester.setup(args=["-t", "udp", "-s", "localhost", "-p", "4567"]) 326 | 327 | # Invalid command for the START state 328 | tester.execute("Hello") 329 | 330 | stdout = tester.get_stdout() 331 | 332 | assert "ERROR: " in stdout, "Output does not match expected output." 333 | 334 | 335 | @testcase 336 | def udp_not_auth(tester): 337 | """Test that the program does not accept any join commands before the user is authenticated.""" 338 | tester.start_server("udp", 4567) 339 | tester.setup(args=["-t", "udp", "-s", "localhost", "-p", "4567"]) 340 | tester.execute("/join") 341 | 342 | # The ERR message should be printed out exactly like this 343 | stdout = tester.get_stdout() 344 | assert any( 345 | ["ERROR:" in line for line in stdout.split("\n")] 346 | ), "Output does not match expected error message." 347 | 348 | 349 | @testcase 350 | def udp_invalid_command(tester): 351 | """Test that the program does not accept invalid commands.""" 352 | tester.setup(args=["-t", "udp", "-s", "localhost", "-p", "4567"]) 353 | 354 | # Invalid command in general 355 | tester.execute("/pepe") 356 | 357 | stdout = tester.get_stdout() 358 | 359 | assert "ERROR:" in stdout, "Output does not match expected output." 360 | 361 | 362 | @testcase 363 | def udp_auth(tester): 364 | """Test that the program sends the correct AUTH message.""" 365 | tester.start_server("udp", 4567) 366 | tester.setup(args=["-t", "udp", "-s", "localhost", "-p", "4567"]) 367 | 368 | # Send AUTH command 369 | tester.execute("/auth a b c") 370 | 371 | # Check AUTH message received 372 | message = tester.receive_message() 373 | tMessage = translateMessage(message) 374 | assert ( 375 | tMessage == "AUTH IS a AS c USING b\r\n" 376 | ), "Incoming message does not match expected message." 377 | 378 | 379 | @testcase 380 | def udp_auth_port(tester): 381 | """Test that the program sends the correct AUTH message when the port is non-default.""" 382 | tester.start_server("udp", 1234) 383 | tester.setup(args=["-t", "udp", "-s", "localhost", "-p", "1234"]) 384 | 385 | # Send AUTH command 386 | tester.execute("/auth a b c") 387 | 388 | # Check AUTH message received 389 | message = tester.receive_message() 390 | tMessage = translateMessage(message) 391 | assert ( 392 | tMessage == "AUTH IS a AS c USING b\r\n" 393 | ), "Incoming message does not match expected message." 394 | 395 | 396 | @testcase 397 | def udp_auth_nok(tester): 398 | """Test that the program handles a NOK reply to AUTH correctly.""" 399 | tester.start_server("udp", 4567) 400 | tester.setup(args=["-t", "udp", "-s", "localhost", "-p", "4567"]) 401 | 402 | # Send AUTH command 403 | tester.execute("/auth a b c") 404 | 405 | # Expect the auth message to be received by the server 406 | message = tester.receive_message() 407 | tMessage = translateMessage(message) 408 | assert ( 409 | tMessage == "AUTH IS a AS c USING b\r\n" 410 | ), "Incoming message does not match expected AUTH message." 411 | 412 | # Reply with NOK 413 | tester.send_message(reply(0, False, getMessageId(message), "nene")) 414 | 415 | sleep(0.2) 416 | 417 | # Check the output, should contain "Action Failure: nene" 418 | stdout = tester.get_stdout() 419 | assert any( 420 | ["Action Failure: nene" in line for line in stdout.split("\n")] 421 | ), "Output does not match expected 'Action Failure: nene' output." 422 | 423 | # Should receive CONFIRM for the REPLY message 424 | message = tester.receive_message() 425 | assert ( 426 | message == b"\x00\x00\x00" 427 | ), "Incoming message does not match expected CONFIRM message." 428 | 429 | @testcase 430 | def udp_rename_multiple(tester): 431 | """Test renaming multiple times and sending a message.""" 432 | auth_and_reply(tester) # Initial auth as 'c' 433 | tester.execute("/rename user1") 434 | sleep(0.2) 435 | tester.execute("/rename user2") 436 | sleep(0.2) 437 | tester.execute("message after rename") 438 | 439 | # Expect MSG with the latest display name 'user2' 440 | message = tester.receive_message() 441 | tMessage = translateMessage(message) 442 | assert ( 443 | tMessage == "MSG FROM user2 IS message after rename\r\n" 444 | ), "Message should be sent with the latest display name 'user2'." 445 | 446 | 447 | @testcase 448 | def udp_auth_nok_ok(tester): 449 | """Test that the program handles a NOK reply to AUTH followed by a Action successful AUTH correctly.""" 450 | tester.start_server("udp", 4567) 451 | tester.setup(args=["-t", "udp", "-s", "localhost", "-p", "4567"]) 452 | 453 | # Send AUTH command 454 | tester.execute("/auth a b c") 455 | 456 | # Expect the auth message to be received by the server 457 | message = tester.receive_message() 458 | tMessage = translateMessage(message) 459 | assert ( 460 | tMessage == "AUTH IS a AS c USING b\r\n" 461 | ), "Incoming message does not match expected AUTH message." 462 | 463 | # Reply with NOK 464 | tester.send_message(reply(0, False, getMessageId(message), "nene")) 465 | 466 | sleep(0.2) 467 | 468 | # Check the output, should contain "Action Failure: nene" 469 | stdout = tester.get_stdout() 470 | assert any( 471 | ["Action Failure: nene" in line for line in stdout.split("\n")] 472 | ), "Output does not match expected 'Action Failure: nene' output." 473 | 474 | # Should receive CONFIRM for the REPLY message 475 | message = tester.receive_message() 476 | assert ( 477 | message == b"\x00\x00\x00" 478 | ), "Incoming message does not match expected CONFIRM message." 479 | 480 | # Send a second AUTH message 481 | tester.execute("/auth a b c") 482 | 483 | # Expect the auth message to be received by the server 484 | message = tester.receive_message() 485 | tMessage = translateMessage(message) 486 | assert ( 487 | tMessage == "AUTH IS a AS c USING b\r\n" 488 | ), "Incoming message does not match expected AUTH message." 489 | 490 | # Reply with OK 491 | tester.send_message(reply(1, True, getMessageId(message), "yes")) 492 | 493 | sleep(0.2) 494 | 495 | # Check the output, should contain "Action Success: yes" 496 | stdout = tester.get_stdout() 497 | assert any( 498 | ["Action Success: yes" in line for line in stdout.split("\n")] 499 | ), "Output does not match expected 'Action Success: yes' output." 500 | 501 | # Should receive CONFIRM for the REPLY message 502 | message = tester.receive_message() 503 | assert ( 504 | message == b"\x00\x00\x01" 505 | ), "Incoming message does not match expected CONFIRM message." 506 | 507 | 508 | @testcase 509 | def udp_auth_port_change(tester): 510 | """Test that the program switches to the new port after receiving an OK reply to AUTH.""" 511 | # Start initial port listener 512 | tmp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 513 | tmp_socket.bind(("localhost", 1234)) 514 | 515 | # Start normal port listener 516 | tester.start_server("udp", 4567) 517 | 518 | # Start client on temp port 519 | tester.setup(args=["-t", "udp", "-s", "localhost", "-p", "1234"]) 520 | 521 | # Send AUTH command 522 | tester.execute("/auth a b c") 523 | 524 | # Expect the auth message to be received by the server on tmp socket 525 | tmp_socket.settimeout(10) 526 | message, client_address = tmp_socket.recvfrom(1024) 527 | # Confirm the AUTH message on tmp socket 528 | tmp_socket.sendto(b"\x00" + getMessageId(message), client_address) 529 | tMessage = translateMessage(message) 530 | assert ( 531 | tMessage == "AUTH IS a AS c USING b\r\n" 532 | ), "Incoming message does not match expected AUTH message." 533 | 534 | # Reply with OK from different port (from now on the client should switch to it) 535 | tester.setClientAddress(client_address) 536 | tester.send_message(reply(0, True, getMessageId(message), "jojo")) 537 | 538 | sleep(0.2) 539 | 540 | # Check the output, should contain "Action Success: jojo" 541 | stdout = tester.get_stdout() 542 | assert any( 543 | ["Action Success: jojo" in line for line in stdout.split("\n")] 544 | ), "Output does not match expected 'Action Success: jojo' output." 545 | 546 | # Should receive CONFIRM for the REPLY message 547 | message = tester.receive_message() 548 | assert ( 549 | message == b"\x00\x00\x00" 550 | ), "Incoming message does not match expected CONFIRM message." 551 | 552 | 553 | # Helper function 554 | def auth_and_reply(tester): 555 | """Helper function to test the AUTH command followed by a Action successful reply.""" 556 | tester.start_server("udp", 4567) 557 | tester.setup(args=["-t", "udp", "-s", "localhost", "-p", "4567"]) 558 | 559 | # Send AUTH command 560 | tester.execute("/auth a b c") 561 | 562 | # Expect the auth message to be received by the server 563 | message = tester.receive_message() 564 | tMessage = translateMessage(message) 565 | assert ( 566 | tMessage == "AUTH IS a AS c USING b\r\n" 567 | ), "Incoming message does not match expected AUTH message." 568 | 569 | # Reply with OK 570 | tester.send_message(reply(0, True, getMessageId(message), "jojo")) 571 | 572 | sleep(0.2) 573 | 574 | # Check the output, should contain "Action Success: jojo" 575 | stdout = tester.get_stdout() 576 | assert any( 577 | ["Action Success: jojo" in line for line in stdout.split("\n")] 578 | ), "Output does not match expected 'Action Success: jojo' output." 579 | 580 | # Should receive CONFIRM for the REPLY message 581 | message = tester.receive_message() 582 | assert ( 583 | message == b"\x00\x00\x00" 584 | ), "Incoming message does not match expected CONFIRM message." 585 | 586 | 587 | @testcase 588 | def udp_auth_ok(tester): 589 | """Test that the program handles a Action successful reply to AUTH correctly.""" 590 | auth_and_reply(tester) 591 | 592 | 593 | @testcase 594 | def udp_msg(tester): 595 | """Test that the program sends the correct MSG message.""" 596 | auth_and_reply(tester) 597 | 598 | tester.execute("ahojky") 599 | 600 | # Expect the message to be received by the server 601 | message = tester.receive_message() 602 | tMessage = translateMessage(message) 603 | assert ( 604 | tMessage == "MSG FROM c IS ahojky\r\n" 605 | ), "Incoming message does not match expected MSG message." 606 | 607 | 608 | @testcase 609 | def udp_svr_msg(tester): 610 | """Test that the program handles a MSG message from the server correctly.""" 611 | auth_and_reply(tester) 612 | 613 | # Send a message from the server 614 | tester.send_message(msg(1, "smrt", "ahojky")) 615 | 616 | sleep(0.2) 617 | 618 | # Check the output, should contain "ahojky" 619 | stdout = tester.get_stdout() 620 | assert any( 621 | ["smrt: ahojky" in line for line in stdout.split("\n")] 622 | ), "Output does not match expected output." 623 | 624 | # Should receive CONFIRM for the MSG message 625 | message = tester.receive_message() 626 | assert ( 627 | message == b"\x00\x00\x01" 628 | ), "Incoming message does not match expected CONFIRM message." 629 | 630 | # New test for IPK25 631 | @testcase 632 | def udp_ping(tester): 633 | """Test that the program handles PING correctly.""" 634 | auth_and_reply(tester) 635 | 636 | tester.send_message(ping(1)) 637 | 638 | 639 | message = tester.receive_message() 640 | assert ( 641 | message == b"\x00\x00\x01" 642 | ), "Incoming message does not match expected CONFIRM message." 643 | 644 | 645 | @testcase 646 | def udp_bye1(tester): 647 | """Test that the program handles SIGINT correctly.""" 648 | auth_and_reply(tester) 649 | 650 | # Send a message from the server 651 | tester.send_signal(signal.SIGINT) 652 | 653 | message = tester.receive_message() 654 | tMessage = translateMessage(message) 655 | 656 | assert ( 657 | tMessage == "BYE FROM c\r\n" 658 | ), "Incoming message does not match expected BYE message." 659 | 660 | 661 | @testcase 662 | def udp_bye2(tester): 663 | """Test that the program handles a C-d (stdin closed) correctly.""" 664 | auth_and_reply(tester) 665 | 666 | # Send a message from the server 667 | tester.process.stdin.close() 668 | 669 | message = tester.receive_message() 670 | tMessage = translateMessage(message) 671 | assert ( 672 | tMessage == "BYE FROM c\r\n" 673 | ), "Incoming message does not match expected BYE message." 674 | 675 | @testcase 676 | def udp_server_bye(tester): 677 | """Test that the client terminates correctly upon receiving BYE from the server.""" 678 | auth_and_reply(tester) 679 | tester.send_message(bye(1, "c")) 680 | sleep(0.2) 681 | # check if the process is no longer running 682 | assert tester.process.poll() is not None, "Client process should terminate after receiving BYE." 683 | # We should also receive a CONFIRM for the BYE message 684 | message = tester.receive_message() 685 | assert ( 686 | message == b"\x00\x00\x01" 687 | ), "Incoming message does not match expected CONFIRM message for BYE." 688 | 689 | @testcase 690 | def udp_send_receive_multiple(tester): 691 | """Test sending and receiving multiple messages.""" 692 | auth_and_reply(tester) 693 | tester.execute("client msg 1") 694 | message = tester.receive_message() 695 | assert "MSG FROM c IS client msg 1\r\n" in translateMessage(message), "Expected client msg 1" 696 | 697 | tester.send_message(msg(1, "server", "server msg 1")) 698 | sleep(0.2) 699 | stdout = tester.get_stdout() 700 | assert "server: server msg 1" in stdout, "Expected server msg 1 in output" 701 | message = tester.receive_message() # Confirm for server msg 1 702 | assert message == b"\x00\x00\x01", "Expected confirm for server msg 1" 703 | 704 | tester.execute("client msg 2") 705 | message = tester.receive_message() 706 | assert "MSG FROM c IS client msg 2\r\n" in translateMessage(message), "Expected client msg 2" 707 | 708 | tester.send_message(msg(2, "server", "server msg 2")) 709 | sleep(0.2) 710 | stdout = tester.get_stdout() 711 | assert "server: server msg 2" in stdout, "Expected server msg 2 in output" 712 | message = tester.receive_message() # Confirm for server msg 2 713 | assert message == b"\x00\x00\x02", "Expected confirm for server msg 2" 714 | 715 | @testcase 716 | def udp_retransmit_missing_confirm1(tester): 717 | """Test that the client retransmits a message if CONFIRM is not received.""" 718 | # Authenticate first 719 | auth_and_reply(tester) 720 | 721 | tester.send_confirm = False 722 | # Client sends a message 723 | tester.execute("test retransmit message") 724 | 725 | # Receive the message, but DO NOT confirm it 726 | message1 = tester.receive_message(timeout=1) 727 | msg_id1 = getMessageId(message1) 728 | assert "MSG FROM c IS test retransmit message\r\n" in translateMessage(message1), "Expected first message" 729 | 730 | # Wait for potential retransmit 731 | try: 732 | message2 = tester.receive_message(timeout=3) 733 | msg_id2 = getMessageId(message2) 734 | assert "MSG FROM c IS test retransmit message\r\n" in translateMessage(message2), "Expected retransmitted message" 735 | assert msg_id1 == msg_id2, "Retransmitted message ID should be the same" 736 | # Now confirm the retransmitted message 737 | tester.confirm(message2) 738 | except TimeoutError: 739 | assert False, "Client did not retransmit the message within the timeout period." 740 | 741 | 742 | @testcase 743 | def udp_retransmit_missing_confirm2(tester): 744 | """Test that the client retransmits a message if CONFIRM is not received.""" 745 | tester.start_server("udp", 4567) 746 | tester.setup(args=["-t", "udp", "-s", "localhost", "-p", "4567"]) 747 | tester.execute("/auth a b c") 748 | 749 | # Receive AUTH, but DO NOT confirm it 750 | tester.send_confirm = False 751 | message1 = tester.receive_message(timeout=1) 752 | msg_id1 = getMessageId(message1) 753 | assert "AUTH IS a AS c USING b\r\n" in translateMessage(message1), "Expected AUTH message" 754 | 755 | # Wait for potential retransmit 756 | try: 757 | message2 = tester.receive_message(timeout=3) 758 | msg_id2 = getMessageId(message2) 759 | assert "AUTH IS a AS c USING b\r\n" in translateMessage(message2), "Expected retransmitted AUTH message" 760 | assert msg_id1 == msg_id2, "Retransmitted message ID should be the same" 761 | # Now confirm the retransmitted message 762 | tester.confirm(message2) 763 | except TimeoutError: 764 | assert False, "Client did not retransmit the message within the timeout period." 765 | 766 | @testcase 767 | def udp_ignore_duplicate_server_msg(tester): 768 | """Test that the client ignores duplicated messages from the server but confirms them.""" 769 | auth_and_reply(tester) 770 | 771 | # Send the same message twice 772 | server_message = msg(1, "server", "duplicate test") 773 | tester.send_message(server_message) 774 | sleep(0.2) 775 | tester.send_message(server_message) # Send duplicate 776 | sleep(0.2) 777 | 778 | # Check output - should only contain the message once 779 | stdout = tester.get_stdout() 780 | occurrences = stdout.count("server: duplicate test") 781 | assert occurrences == 1, f"Expected message to appear once in output, found {occurrences} times." 782 | 783 | # Check confirmations - should receive two confirms for the same message ID 784 | confirm1 = tester.receive_message() 785 | assert confirm1 == b"\x00\x00\x01", "Expected confirm for first message" 786 | confirm2 = tester.receive_message() 787 | assert confirm2 == b"\x00\x00\x01", "Expected confirm for duplicate message" 788 | 789 | @testcase 790 | def udp_reports_error_when_missing_confirmation(tester): 791 | """Test that the client retransmits a message if CONFIRM is not received and also Error when client dont recieve CONFIRM.""" 792 | auth_and_reply(tester) 793 | 794 | # Receive AUTH, but DO NOT confirm it 795 | tester.send_confirm = False 796 | tester.execute("Hello!") 797 | sleep(2) 798 | stdout = tester.get_stdout() 799 | print(stdout) 800 | assert any( 801 | ["ERROR: " in line for line in stdout.split("\n")] 802 | ), "Output does not match expected 'ERROR: ' output." 803 | 804 | 805 | 806 | 807 | @testcase 808 | def udp_server_err1(tester): 809 | """Test that the program handles an ERR message from the server correctly while waiting for REPLY.""" 810 | tester.start_server("udp", 4567) 811 | tester.setup(args=["-t", "udp", "-s", "localhost", "-p", "4567"]) 812 | 813 | tester.execute("/auth a b c") 814 | 815 | # Should have received the AUTH message 816 | message = tester.receive_message() 817 | tMessage = translateMessage(message) 818 | assert ( 819 | tMessage == "AUTH IS a AS c USING b\r\n" 820 | ), "Incoming message does not match expected AUTH message." 821 | 822 | sleep(0.2) 823 | 824 | # Send an ERR message from the server 825 | tester.send_message(err(0, "server", "chyba")) 826 | 827 | sleep(0.2) 828 | 829 | stdout = tester.get_stdout() 830 | assert any( 831 | ["ERROR FROM server: chyba" in line for line in stdout.split("\n")] 832 | ), "Output does not match expected error message." 833 | 834 | # Should receive CONFIRM for the ERR message 835 | message = tester.receive_message() 836 | assert ( 837 | message == b"\x00\x00\x00" 838 | ), "Incoming message does not match expected CONFIRM message." 839 | 840 | # Should receive BYE for the ERR message 841 | # !!!Based on new FSM BYE is not mandatory anymore!!! 842 | # message = tester.receive_message() 843 | # tMessage = translateMessage(message) 844 | # assert ( 845 | # tMessage == "BYE FROM c\r\n" 846 | # ), "Incoming message does not match expected BYE message." 847 | 848 | 849 | @testcase 850 | def udp_server_err2(tester): 851 | """Test that the program handles an ERR message from the server correctly.""" 852 | auth_and_reply(tester) 853 | 854 | # Send a message from the server 855 | tester.send_message(err(1, "server", "chyba")) 856 | 857 | sleep(0.2) 858 | 859 | # The ERR message should be printed out exactly like this 860 | stdout = tester.get_stdout() 861 | assert any( 862 | ["ERROR FROM server: chyba" in line for line in stdout.split("\n")] 863 | ), "Output does not match expected error message." 864 | 865 | # Should receive CONFIRM for the ERROR message 866 | message = tester.receive_message() 867 | assert ( 868 | message == b"\x00\x00\x01" 869 | ), "Incoming message does not match expected CONFIRM message." 870 | 871 | # Should receive BYE for the ERROR message 872 | # !!!Based on new FSM BYE is not mandatory anymore!!! 873 | # message = tester.receive_message() 874 | # tMessage = translateMessage(message) 875 | # assert ( 876 | # tMessage == "BYE FROM c\r\n" 877 | # ), "Incoming message does not match expected BYE message." 878 | 879 | 880 | @testcase 881 | def udp_join_ok(tester): 882 | """Test that the program sends the correct JOIN message and hadles REPLY correctly.""" 883 | auth_and_reply(tester) 884 | 885 | tester.execute("/rename user") 886 | 887 | tester.execute("/join channel") 888 | 889 | # Expect the join message to be received by the server 890 | message = tester.receive_message() 891 | tMessage = translateMessage(message) 892 | assert ( 893 | tMessage == "JOIN IS channel AS user\r\n" 894 | ), "Incoming message does not match expected JOIN message." 895 | 896 | # Send REPLY message 897 | tester.send_message(reply(1, True, getMessageId(message), "jojo")) 898 | 899 | sleep(0.2) 900 | 901 | # Check the output, should contain "Action Success: jojo" 902 | stdout = tester.get_stdout() 903 | assert any( 904 | ["Action Success: jojo" in line for line in stdout.split("\n")] 905 | ), "Output does not match expected 'Action Success: jojo' output." 906 | 907 | # Should receive CONFIRM for the REPLY message 908 | message = tester.receive_message() 909 | assert ( 910 | message == b"\x00\x00\x01" 911 | ), "Incoming message does not match expected CONFIRM message." 912 | 913 | 914 | @testcase 915 | def udp_join_nok(tester): 916 | """Test that the program sends the correct JOIN message and handles a NOK reply correctly.""" 917 | auth_and_reply(tester) 918 | 919 | tester.execute("/rename user") 920 | 921 | tester.execute("/join channel") 922 | 923 | # Expect the join message to be received by the server 924 | message = tester.receive_message() 925 | tMessage = translateMessage(message) 926 | assert ( 927 | tMessage == "JOIN IS channel AS user\r\n" 928 | ), "Incoming message does not match expected JOIN message." 929 | 930 | # Send REPLY message 931 | tester.send_message(reply(1, False, getMessageId(message), "nene")) 932 | 933 | sleep(0.2) 934 | 935 | # Check the output, should contain "Action Failure: nene" 936 | stdout = tester.get_stdout() 937 | assert any( 938 | ["Action Failure: nene" in line for line in stdout.split("\n")] 939 | ), "Output does not match expected 'Action Failure: nene' output." 940 | 941 | # Should receive CONFIRM for the REPLY message 942 | message = tester.receive_message() 943 | assert ( 944 | message == b"\x00\x00\x01" 945 | ), "Incoming message does not match expected CONFIRM message." 946 | 947 | 948 | @testcase 949 | def udp_multiple_auth(tester): 950 | """Test that the program does not allow multiple AUTH commands.""" 951 | auth_and_reply(tester) 952 | 953 | tester.execute("/auth d e f") 954 | 955 | sleep(0.2) 956 | 957 | # Client should not allow another auth and should output ERR 958 | stdout = tester.get_stdout() 959 | assert any( 960 | ["ERROR: " in line for line in stdout.split("\n")] 961 | ), "Output does not match expected 'ERROR: ' output." 962 | 963 | 964 | @testcase 965 | def udp_invalid_msg(tester): 966 | """Test that the program handles an invalid message correctly.""" 967 | auth_and_reply(tester) 968 | 969 | # Send invalid message 970 | tester.send_message(b"\x06\x00\x01") 971 | 972 | sleep(0.2) 973 | 974 | # Check the output, should contain "ERROR: " 975 | stdout = tester.get_stdout() 976 | assert any( 977 | ["ERROR: " in line for line in stdout.split("\n")] 978 | ), "Output does not match expected 'ERROR: ' output." 979 | 980 | # we should firs expect confirm (Packet loss can be detected using mandatory 981 | # message confirmation with timeouts. Once a message is sent it is required to confirm its Action successful delivery by the other party. 982 | # The confirmation should be sent immediately after receiving the message, regardless of any potential higher-level processing issues) 983 | message = tester.receive_message() 984 | assert ( 985 | message == b"\x00\x00\x01" 986 | ), "Incoming message does not match expected CONFIRM message." 987 | 988 | # Should receive ERR for the invalid message 989 | # !!!Based on new FSM BYE is not mandatory anymore!!! 990 | message = tester.receive_message() 991 | tMessage = translateMessage(message) 992 | assert ( 993 | "ERR FROM c IS" in tMessage 994 | ), "Incoming message does not match expected ERR message." 995 | 996 | 997 | @testcase 998 | def udp_auth_err(tester): 999 | """Test that the program handles an ERR message from the server correctly.""" 1000 | tester.start_server("udp", 4567) 1001 | tester.setup(args=["-t", "udp", "-s", "localhost", "-p", "4567"]) 1002 | 1003 | # Send AUTH command 1004 | tester.execute("/auth a b c") 1005 | 1006 | # Expect the auth message to be received by the server 1007 | message = tester.receive_message() 1008 | tMessage = translateMessage(message) 1009 | assert ( 1010 | tMessage == "AUTH IS a AS c USING b\r\n" 1011 | ), "Incoming message does not match expected AUTH message." 1012 | 1013 | # Send ERR message 1014 | tester.send_message(err(1, "server", "ajaj")) 1015 | 1016 | sleep(0.2) 1017 | 1018 | # The client should output the ERR message exactly like this 1019 | stdout = tester.get_stdout() 1020 | assert any( 1021 | ["ERROR FROM server: ajaj" in line for line in stdout.split("\n")] 1022 | ), "Output does not match expected error message." 1023 | 1024 | # Check confirm of the ERR message 1025 | message = tester.receive_message() 1026 | assert ( 1027 | message == b"\x00\x00\x01" 1028 | ), "Incoming message does not match expected CONFIRM message." 1029 | 1030 | # The client should respond with BYE message 1031 | # !!!Based on new FSM BYE is not mandatory anymore!!! 1032 | # message = tester.receive_message() 1033 | # tMessage = translateMessage(message) 1034 | # assert ( 1035 | # tMessage == "BYE FROM c\r\n" 1036 | # ), "Incoming message does not match expected BYE message." 1037 | 1038 | 1039 | # PART 3: TCP 1040 | 1041 | @testcase 1042 | def tcp_sigint(tester): 1043 | """Test that the program handles SIGINT correctly.""" 1044 | tcp_auth_and_reply(tester) 1045 | 1046 | # Send SIGINT signal 1047 | tester.send_signal(signal.SIGINT) 1048 | 1049 | # Expect BYE message 1050 | message = tester.receive_message() 1051 | assert message == "BYE FROM c\r\n", "Incoming message does not match expected BYE message after SIGINT." 1052 | 1053 | @testcase 1054 | def tcp_server_bye(tester): 1055 | """Test that the client terminates correctly upon receiving BYE from the server.""" 1056 | tcp_auth_and_reply(tester) 1057 | tester.send_message("BYE FROM server\r\n") # Server sends BYE 1058 | sleep(0.2) 1059 | # Client should terminate 1060 | assert tester.process.poll() is not None, "Client process should terminate after receiving BYE." 1061 | 1062 | @testcase 1063 | def tcp_rename(tester): 1064 | """Test renaming and sending a message with the new name.""" 1065 | tcp_auth_and_reply(tester) # Initial auth as 'c' 1066 | tester.execute("/rename user1") 1067 | sleep(0.2) 1068 | tester.execute("message after rename") 1069 | 1070 | # Expect MSG with the new display name 'user1' 1071 | message = tester.receive_message() 1072 | assert ( 1073 | message == "MSG FROM user1 IS message after rename\r\n" 1074 | ), "Message should be sent with the new display name 'user1'." 1075 | 1076 | @testcase 1077 | def tcp_rename_multiple(tester): 1078 | """Test renaming multiple times and sending a message.""" 1079 | tcp_auth_and_reply(tester) # Initial auth as 'c' 1080 | tester.execute("/rename user1") 1081 | sleep(0.2) 1082 | tester.execute("/rename user2") 1083 | sleep(0.2) 1084 | tester.execute("message after rename") 1085 | 1086 | # Expect MSG with the latest display name 'user2' 1087 | message = tester.receive_message() 1088 | assert ( 1089 | message == "MSG FROM user2 IS message after rename\r\n" 1090 | ), "Message should be sent with the latest display name 'user2'." 1091 | 1092 | @testcase 1093 | def tcp_help_command(tester): 1094 | """Test that the /help command shows some output.""" 1095 | tcp_auth_and_reply(tester) 1096 | tester.execute("/help") 1097 | sleep(1) 1098 | stdout = tester.get_stdout() 1099 | assert len(stdout.strip()) > 0, "Expected some output for /help command." 1100 | 1101 | @testcase 1102 | def tcp_send_receive_multiple(tester): 1103 | """Test sending and receiving multiple messages.""" 1104 | tcp_auth_and_reply(tester) 1105 | tester.execute("client msg 1") 1106 | message = tester.receive_message() 1107 | assert message == "MSG FROM c IS client msg 1\r\n", "Expected client msg 1" 1108 | 1109 | tester.send_message("MSG FROM server IS server msg 1\r\n") 1110 | sleep(0.2) 1111 | stdout = tester.get_stdout() 1112 | assert "server: server msg 1" in stdout, "Expected server msg 1 in output" 1113 | 1114 | tester.execute("client msg 2") 1115 | message = tester.receive_message() 1116 | assert message == "MSG FROM c IS client msg 2\r\n", "Expected client msg 2" 1117 | 1118 | tester.send_message("MSG FROM server IS server msg 2\r\n") 1119 | sleep(0.2) 1120 | stdout = tester.get_stdout() 1121 | assert "server: server msg 2" in stdout, "Expected server msg 2 in output" 1122 | 1123 | @testcase 1124 | def tcp_multiple_messages_single_segment(tester): 1125 | """Test receiving multiple messages concatenated in a single TCP segment.""" 1126 | tcp_auth_and_reply(tester) 1127 | 1128 | tester.send_message("MSG FROM server1 IS msg1\r\nMSG FROM server2 IS msg2\r\n") 1129 | sleep(1) 1130 | 1131 | stdout = tester.get_stdout() 1132 | assert "server1: msg1" in stdout, "Expected first message in output." 1133 | assert "server2: msg2" in stdout, "Expected second message in output." 1134 | 1135 | 1136 | @testcase 1137 | def tcp_single_message_multiple_segments2(tester): 1138 | """Test receiving a single message split across multiple TCP segments.""" 1139 | tcp_auth_and_reply(tester) 1140 | 1141 | tester.send_message("M") 1142 | sleep(0.2) # Short delay 1143 | tester.send_message("SG ") 1144 | sleep(0.2) # Short delay 1145 | tester.send_message("FR") 1146 | sleep(0.2) # Short delay 1147 | tester.send_message("OM ser") 1148 | sleep(0.2) # Short delay 1149 | tester.send_message("ve") 1150 | sleep(0.2) # Short delay 1151 | tester.send_message("r I") 1152 | sleep(0.2) # Short delay 1153 | tester.send_message("S okeyBroChill\r\n") # Send second part 1154 | 1155 | sleep(0.2) 1156 | stdout = tester.get_stdout() 1157 | assert "server: okeyBroChill" in stdout, "Expected reassembled message in output." 1158 | 1159 | def tcp_single_message_multiple_segments(tester): 1160 | """Test receiving a single message split across multiple TCP segments.""" 1161 | tcp_auth_and_reply(tester) 1162 | 1163 | tester.send_message("MSG FROM server IS part1") 1164 | sleep(0.2) # Short delay 1165 | tester.send_message("part2\r\n") # Send second part 1166 | 1167 | sleep(0.2) 1168 | stdout = tester.get_stdout() 1169 | assert "server: part1part2" in stdout, "Expected reassembled message in output." 1170 | 1171 | @testcase 1172 | def tcp_receive_two_messages_within_3_segments(tester): 1173 | tcp_auth_and_reply(tester) 1174 | 1175 | tester.send_message("MSG FROM teSter ") 1176 | sleep(0.2) # Short delay 1177 | tester.send_message("IS message1\r\nMSG FROM teSte") 1178 | sleep(0.2) # Short delay 1179 | tester.send_message(" IS message2\r\n") 1180 | sleep(0.2) 1181 | stdout = tester.get_stdout() 1182 | assert "teSter: message1\nteSte: message2" in stdout, "Expected reassembled message in output." 1183 | 1184 | 1185 | 1186 | @testcase 1187 | def tcp_hello(tester): 1188 | """Test that the program does not accept any message commands before the user is authenticated.""" 1189 | tester.start_server("tcp", 4567) 1190 | tester.setup(args=["-t", "tcp", "-s", "localhost", "-p", "4567"]) 1191 | 1192 | # Invalid command for the START state 1193 | tester.execute("Hello") 1194 | 1195 | stdout = tester.get_stdout() 1196 | 1197 | assert "ERROR:" in stdout, "Output does not match expected output." 1198 | 1199 | 1200 | @testcase 1201 | def tcp_not_auth(tester): 1202 | """Test that the program does not accept any join commands before the user is authenticated.""" 1203 | tester.start_server("tcp", 4567) 1204 | tester.setup(args=["-t", "tcp", "-s", "localhost", "-p", "4567"]) 1205 | tester.execute("/join") 1206 | 1207 | # The ERR message should be printed out exactly like this 1208 | stdout = tester.get_stdout() 1209 | assert any( 1210 | ["ERROR:" in line for line in stdout.split("\n")] 1211 | ), "Output does not match expected error message." 1212 | 1213 | 1214 | @testcase 1215 | def tcp_invalid_command(tester): 1216 | """Test that the program does not accept invalid commands.""" 1217 | tester.start_server("tcp", 4567) 1218 | tester.setup(args=["-t", "tcp", "-s", "localhost", "-p", "4567"]) 1219 | 1220 | # Invalid command in general 1221 | tester.execute("/pepe") 1222 | 1223 | stdout = tester.get_stdout() 1224 | 1225 | assert "ERROR:" in stdout, "Output does not match expected output." 1226 | 1227 | 1228 | @testcase 1229 | def tcp_auth(tester): 1230 | """Test that the program sends the correct AUTH message.""" 1231 | tester.start_server("tcp", 4567) 1232 | tester.setup(args=["-t", "tcp", "-s", "localhost", "-p", "4567"]) 1233 | tester.execute("/auth a b c") 1234 | 1235 | message = tester.receive_message() 1236 | assert ( 1237 | message == "AUTH a AS c USING b\r\n" 1238 | ), "Incoming message does not match expected AUTH message." 1239 | 1240 | 1241 | @testcase 1242 | def tcp_auth_ok(tester): 1243 | """Test that the program handles a Action successful reply to AUTH correctly.""" 1244 | tester.start_server("tcp", 4567) 1245 | tester.setup(args=["-t", "tcp", "-s", "localhost", "-p", "4567"]) 1246 | tester.execute("/auth a b c") 1247 | 1248 | # Check AUTH message received 1249 | message = tester.receive_message() 1250 | assert ( 1251 | message == "AUTH a AS c USING b\r\n" 1252 | ), "Incoming message does not match expected AUTH message." 1253 | 1254 | # Send REPLY message 1255 | tester.send_message("REPLY OK IS vsechno cajk\r\n") 1256 | 1257 | sleep(0.2) 1258 | 1259 | # Check the output, should contain "Action Success: vsechno cajk" 1260 | stdout = tester.get_stdout() 1261 | assert any( 1262 | ["Action Success: vsechno cajk" == line for line in stdout.split("\n")] 1263 | ), "Output does not match expected error message." 1264 | 1265 | 1266 | @testcase 1267 | def tcp_auth_nok(tester): 1268 | """Test that the program handles a NOK reply to AUTH correctly.""" 1269 | tester.start_server("tcp", 4567) 1270 | tester.setup(args=["-t", "tcp", "-s", "localhost", "-p", "4567"]) 1271 | tester.execute("/auth a b c") 1272 | 1273 | message = tester.receive_message() 1274 | assert ( 1275 | message == "AUTH a AS c USING b\r\n" 1276 | ), "Incoming message does not match expected AUTH message." 1277 | 1278 | tester.send_message("REPLY NOK IS nic cajk\r\n") 1279 | 1280 | sleep(0.2) 1281 | 1282 | stdout = tester.get_stdout() 1283 | assert any( 1284 | ["Action Failure: nic cajk" == line for line in stdout.split("\n")] 1285 | ), "Output does not match expected error message." 1286 | 1287 | 1288 | @testcase 1289 | def tcp_auth_port(tester): 1290 | """Test that the program sends the correct AUTH message when the port is non-default.""" 1291 | tester.start_server("tcp", 4321) 1292 | tester.setup(args=["-t", "tcp", "-s", "localhost", "-p", "4321"]) 1293 | 1294 | # Send AUTH command 1295 | tester.execute("/auth a b c") 1296 | 1297 | # Check AUTH message received 1298 | message = tester.receive_message() 1299 | assert ( 1300 | message == "AUTH a AS c USING b\r\n" 1301 | ), "Incoming message does not match expected message." 1302 | 1303 | 1304 | @testcase 1305 | def tcp_auth_nok_ok(tester): 1306 | """Test that the program handles a NOK reply to AUTH followed by a Action successful AUTH correctly.""" 1307 | tester.start_server("tcp", 4567) 1308 | tester.setup(args=["-t", "tcp", "-s", "localhost", "-p", "4567"]) 1309 | tester.execute("/auth a b c") 1310 | 1311 | message = tester.receive_message() 1312 | assert ( 1313 | message == "AUTH a AS c USING b\r\n" 1314 | ), "Incoming message does not match expected AUTH message." 1315 | 1316 | tester.send_message("REPLY NOK IS nic cajk\r\n") 1317 | 1318 | sleep(0.2) 1319 | 1320 | stdout = tester.get_stdout() 1321 | assert any( 1322 | ["Action Failure: nic cajk" == line for line in stdout.split("\n")] 1323 | ), "Output does not match expected error message." 1324 | 1325 | tester.execute("/auth d e f") 1326 | 1327 | message = tester.receive_message() 1328 | assert ( 1329 | message == "AUTH d AS f USING e\r\n" 1330 | ), "Incoming message does not match expected AUTH message." 1331 | 1332 | tester.send_message("REPLY OK IS vsechno cajk\r\n") 1333 | 1334 | sleep(0.2) 1335 | 1336 | stdout = tester.get_stdout() 1337 | assert any( 1338 | ["Action Success: vsechno cajk" == line for line in stdout.split("\n")] 1339 | ), "Output does not match expected error message." 1340 | 1341 | 1342 | # Helper function 1343 | def tcp_auth_and_reply(tester): 1344 | """Helper function to test the AUTH command followed by a Action successful reply.""" 1345 | tester.start_server("tcp", 4567) 1346 | tester.setup(args=["-t", "tcp", "-s", "localhost", "-p", "4567"]) 1347 | 1348 | # Send AUTH command 1349 | tester.execute("/auth a b c") 1350 | 1351 | # Expect the auth message to be received by the server 1352 | message = tester.receive_message() 1353 | assert ( 1354 | message == "AUTH a AS c USING b\r\n" 1355 | ), "Incoming message does not match expected AUTH message." 1356 | 1357 | # Send REPLY message 1358 | tester.send_message("REPLY OK IS vsechno cajk\r\n") 1359 | 1360 | sleep(0.2) 1361 | 1362 | # Check the output, should contain "Action Success: vsechno cajk" 1363 | stdout = tester.get_stdout() 1364 | assert any( 1365 | ["Action Success: vsechno cajk" == line for line in stdout.split("\n")] 1366 | ), "Output does not match expected error message." 1367 | 1368 | 1369 | @testcase 1370 | def tcp_auth_ok(tester): 1371 | """Test that the program handles a Action successful reply to AUTH correctly.""" 1372 | tcp_auth_and_reply(tester) 1373 | 1374 | 1375 | @testcase 1376 | def tcp_msg(tester): 1377 | """Test that the program sends the correct MSG message.""" 1378 | tcp_auth_and_reply(tester) 1379 | 1380 | tester.execute("ahojky") 1381 | 1382 | # Expect the message to be received by the server 1383 | message = tester.receive_message() 1384 | assert ( 1385 | message == "MSG FROM c IS ahojky\r\n" 1386 | ), "Incoming message does not match expected MSG message." 1387 | 1388 | 1389 | @testcase 1390 | def tcp_svr_msg(tester): 1391 | """Test that the program handles a MSG message from the server correctly.""" 1392 | tcp_auth_and_reply(tester) 1393 | 1394 | # Send a message from the server 1395 | tester.send_message("MSG FROM SeverusSnape IS ahojky\r\n") 1396 | 1397 | sleep(0.2) 1398 | 1399 | # Check the output, should contain "ahojky" 1400 | stdout = tester.get_stdout() 1401 | assert any( 1402 | ["SeverusSnape: ahojky" in line for line in stdout.split("\n")] 1403 | ), "Output does not match expected output." 1404 | 1405 | 1406 | @testcase 1407 | def tcp_bye(tester): 1408 | """Test that the program handles a C-d (stdin closed) correctly.""" 1409 | tcp_auth_and_reply(tester) 1410 | 1411 | # Send a message from the server 1412 | tester.process.stdin.close() 1413 | 1414 | message = tester.receive_message() 1415 | assert message == "BYE FROM c\r\n", "Incoming message does not match expected BYE message." 1416 | 1417 | 1418 | @testcase 1419 | def tcp_server_err1(tester): 1420 | """Test that the program handles an ERR message from the server correctly while waiting for REPLY.""" 1421 | tester.start_server("tcp", 4567) 1422 | tester.setup(args=["-t", "tcp", "-s", "localhost", "-p", "4567"]) 1423 | 1424 | # Execute AUTH command 1425 | tester.execute("/auth a b c") 1426 | 1427 | # Send a message from the server 1428 | tester.send_message("ERR FROM jetovp IS rdeli\r\n") 1429 | 1430 | sleep(0.4) 1431 | 1432 | stdout = tester.get_stdout() 1433 | assert any( 1434 | ["ERROR FROM jetovp: rdeli" in line for line in stdout.split("\n")] 1435 | ), "Output does not match expected error message." 1436 | 1437 | 1438 | @testcase 1439 | def tcp_server_err2(tester): 1440 | """Test that the program handles an ERR message from the server correctly.""" 1441 | tcp_auth_and_reply(tester) 1442 | 1443 | # Send a message from the server 1444 | 1445 | tester.send_message("ERR FROM chuj IS bobr\r\n") 1446 | 1447 | sleep(0.4) 1448 | 1449 | stdout = tester.get_stdout() 1450 | assert any( 1451 | ["ERROR FROM chuj: bobr" in line for line in stdout.split("\n")] 1452 | ), "Output does not match expected error message." 1453 | 1454 | 1455 | @testcase 1456 | def tcp_join_ok(tester): 1457 | """Test that the program sends the correct JOIN message and handles REPLY correctly.""" 1458 | tcp_auth_and_reply(tester) 1459 | 1460 | tester.execute("/rename user") 1461 | 1462 | tester.execute("/join channel") 1463 | 1464 | # Expect the join message to be received by the server 1465 | message = tester.receive_message() 1466 | assert ( 1467 | message == "JOIN channel AS user\r\n" 1468 | ), "Incoming message does not match expected JOIN message." 1469 | 1470 | # Send REPLY message 1471 | tester.send_message("REPLY OK IS takjo brasko ale bud hodnej\r\n") 1472 | 1473 | sleep(0.2) 1474 | 1475 | # Check the output, should contain "Action Success: jojo" 1476 | stdout = tester.get_stdout() 1477 | assert any( 1478 | ["Action Success: takjo brasko ale bud hodnej" in line for line in stdout.split("\n")] 1479 | ), "Output does not match expected 'Action Success: jojo' output." 1480 | 1481 | 1482 | @testcase 1483 | def tcp_join_nok(tester): 1484 | """Test that the program sends the correct JOIN message and handles a NOK reply correctly.""" 1485 | tcp_auth_and_reply(tester) 1486 | 1487 | tester.execute("/rename user") 1488 | 1489 | tester.execute("/join channel") 1490 | 1491 | # Expect the join message to be received by the server 1492 | message = tester.receive_message() 1493 | assert ( 1494 | message == "JOIN channel AS user\r\n" 1495 | ), "Incoming message does not match expected JOIN message." 1496 | 1497 | # Send REPLY message 1498 | tester.send_message("REPLY NOK IS minus boiiii\r\n") 1499 | 1500 | sleep(0.2) 1501 | 1502 | # Check the output, should contain "Action Success: jojo" 1503 | stdout = tester.get_stdout() 1504 | assert any( 1505 | ["Action Failure: minus boiiii" in line for line in stdout.split("\n")] 1506 | ), "Output does not match expected 'Action Success: jojo' output." 1507 | 1508 | 1509 | @testcase 1510 | def tcp_multiple_auth(tester): 1511 | """Test that the program does not allow multiple AUTH commands.""" 1512 | tcp_auth_and_reply(tester) 1513 | 1514 | tester.execute("/auth d e f") 1515 | 1516 | sleep(0.2) 1517 | 1518 | # Client should not allow another auth and should output ERR 1519 | stdout = tester.get_stdout() 1520 | assert any( 1521 | ["ERROR: " in line for line in stdout.split("\n")] 1522 | ), "Output does not match expected 'ERROR: ' output." 1523 | 1524 | 1525 | @testcase 1526 | def tcp_invalid_msg(tester): 1527 | """Test that the program handles an invalid message correctly and responds appropriately.""" 1528 | 1529 | tcp_auth_and_reply(tester) 1530 | 1531 | # Send an invalid message 1532 | tester.send_message("TVOJE MAMINKA\r\n") 1533 | 1534 | sleep(0.2) 1535 | 1536 | # Check stdout contains an appropriate error message 1537 | stdout = tester.get_stdout() 1538 | assert any( 1539 | "ERROR: " in line for line in stdout.split("\n") 1540 | ), "Output does not contain expected 'ERROR: ' message." 1541 | 1542 | # Collect responses (messages may be split or combined) 1543 | received = "" 1544 | for _ in range(2): # Try to receive up to 2 packets 1545 | try: 1546 | part = tester.receive_message(timeout=0.5) 1547 | if part: 1548 | received += part 1549 | except TimeoutError: 1550 | break 1551 | 1552 | 1553 | # Use regex to validate both messages are present 1554 | # !!!Based on new FSM BYE is not mandatory anymore!!! 1555 | err_match = re.search(r"ERR FROM c IS [ -~]+\r\n", received) 1556 | # bye_match = re.search(r"BYE FROM c\r\n", received) 1557 | 1558 | assert err_match, "Missing or malformed ERR message." 1559 | # assert bye_match, "Missing BYE message." 1560 | 1561 | # Close client's stdin 1562 | tester.process.stdin.close() 1563 | sleep(0.2) 1564 | 1565 | @testcase 1566 | def tcp_auth_err(tester): 1567 | """Test that the program handles an ERR message from the server correctly.""" 1568 | tester.start_server("tcp", 4567) 1569 | tester.setup(args=["-t", "tcp", "-s", "localhost", "-p", "4567"]) 1570 | tester.execute("/auth a b c") 1571 | 1572 | # Expect the auth message to be received by the server 1573 | message = tester.receive_message() 1574 | assert ( 1575 | message == "AUTH a AS c USING b\r\n" 1576 | ), "Incoming message does not match expected AUTH message." 1577 | 1578 | # Send ERR message 1579 | tester.send_message("ERR FROM server IS ajaj\r\n") 1580 | sleep(0.2) 1581 | 1582 | # The client should output the ERR message exactly like this 1583 | stdout = tester.get_stdout() 1584 | assert any( 1585 | ["ERROR FROM server: ajaj" in line for line in stdout.split("\n")] 1586 | ), "Output does not match expected error message." 1587 | 1588 | # The client should respond with BYE message 1589 | # !!!Based on new FSM BYE is not mandatory anymore!!! 1590 | # message = tester.receive_message() 1591 | # assert message == "BYE FROM c\r\n", "Incoming message does not match expected BYE message." 1592 | 1593 | @testcase 1594 | def tcp_grammar_is_case_insensitive(tester): 1595 | """Test verifies that TCP grammar is case-insensitive for REPLY, MSG, and ERR server packets.""" 1596 | 1597 | # Start mock TCP server and initialize client 1598 | tester.start_server("tcp", 4567) 1599 | tester.setup(args=["-t", "tcp", "-s", "localhost", "-p", "4567"]) 1600 | tester.execute("/auth a b c") 1601 | 1602 | # Validate that AUTH command is sent correctly 1603 | message = tester.receive_message() 1604 | assert message == "AUTH a AS c USING b\r\n", "AUTH message mismatch." 1605 | 1606 | # 1. Send a mixed-case REPLY message 1607 | tester.send_message("rEpLy oK is authenticated\r\n") 1608 | sleep(0.2) 1609 | stdout = tester.get_stdout() 1610 | assert any("Action Success: authenticated" in line for line in stdout.split("\n")), \ 1611 | "Client did not report successful reply." 1612 | 1613 | # 2. Send a mixed-case MSG message 1614 | tester.send_message("mSg fRoM teSter Is message\r\n") 1615 | sleep(0.2) 1616 | stdout = tester.get_stdout() 1617 | assert any("teSter: message" in line for line in stdout.split("\n")), \ 1618 | "Client did not receive expected message." 1619 | 1620 | # 3. Send a mixed-case ERR message 1621 | tester.send_message("eRR FrOm teSter iS message\r\n") 1622 | sleep(0.2) 1623 | stdout = tester.get_stdout() 1624 | assert any("ERROR FROM teSter: message" in line for line in stdout.split("\n")), \ 1625 | "Client did not report expected error message." 1626 | 1627 | 1628 | 1629 | ### END TEST CASES ### 1630 | 1631 | 1632 | def run_tests(executable_path, udp=False, tcp=False, test_case=None): 1633 | test_cases_passed = 0 1634 | tester = ExecutableTester(executable_path) 1635 | 1636 | total_cases = 0 1637 | 1638 | if test_case is None: 1639 | # Test UDP 1640 | if udp: 1641 | total_cases += len(udp_test_cases) 1642 | for test in udp_test_cases: 1643 | tester = ExecutableTester(executable_path) 1644 | test_cases_passed += 1 if test(tester) else 0 1645 | 1646 | # Test TCP 1647 | if tcp: 1648 | total_cases += len(tcp_test_cases) 1649 | for test in tcp_test_cases: 1650 | tester = ExecutableTester(executable_path) 1651 | test_cases_passed += 1 if test(tester) else 0 1652 | 1653 | else: 1654 | test_found = False 1655 | 1656 | # Find and run the specified UDP test case 1657 | if udp: 1658 | for udp_test in udp_test_cases: 1659 | if udp_test.__name__ == test_case: 1660 | total_cases += 1 1661 | test_cases_passed += 1 if udp_test(tester) else 0 1662 | test_found = True 1663 | break 1664 | 1665 | # Find and run the specified TCP test case if it wasn't found in UDP 1666 | if tcp and not test_found: 1667 | for tcp_test in tcp_test_cases: 1668 | if tcp_test.__name__ == test_case: 1669 | total_cases += 1 1670 | test_cases_passed += 1 if tcp_test(tester) else 0 1671 | test_found = True 1672 | break 1673 | 1674 | if not test_found: 1675 | print( 1676 | f"Error: Test case '{test_case}' not found in the specified protocol tests." 1677 | ) 1678 | return 1679 | 1680 | cprint( 1681 | f"\n{'✅' if test_cases_passed == total_cases else '❌'} {test_cases_passed}/{total_cases} test cases passed", 1682 | "green" if test_cases_passed == total_cases else "red", 1683 | ) 1684 | 1685 | 1686 | if __name__ == "__main__": 1687 | # Default settings 1688 | debug = False 1689 | udp = True 1690 | tcp = True 1691 | test_case = None 1692 | 1693 | executable_path = None 1694 | 1695 | if "-h" in sys.argv: 1696 | print( 1697 | "Usage: python test_executable.py [-d: debug] [-t ] [-p ]" 1698 | ) 1699 | sys.exit(0) 1700 | 1701 | args = iter(sys.argv[1:]) # Skip the script name 1702 | for arg in args: 1703 | if arg == "-d": 1704 | debug = True 1705 | elif arg == "-t": 1706 | try: 1707 | test_case = next(args) # Get the next argument as the test case name 1708 | if test_case not in [ 1709 | tc.__name__ for tc in udp_test_cases 1710 | ] and test_case not in [tc.__name__ for tc in tcp_test_cases]: 1711 | print(f"Error: Test case '{test_case}' not recognized.") 1712 | sys.exit(1) 1713 | except StopIteration: 1714 | print("Error: -t requires a test case name.") 1715 | sys.exit(1) 1716 | elif arg == "-p": 1717 | try: 1718 | protocol = next(args).lower() # Get the next argument as the protocol 1719 | if protocol == "udp": 1720 | tcp = False 1721 | elif protocol == "tcp": 1722 | udp = False 1723 | else: 1724 | print("Error: Protocol must be either 'udp' or 'tcp'.") 1725 | sys.exit(1) 1726 | except StopIteration: 1727 | print("Error: -p requires a protocol name (udp or tcp).") 1728 | sys.exit(1) 1729 | else: 1730 | if executable_path is None: 1731 | executable_path = arg 1732 | else: 1733 | print("Error: Unexpected argument or executable path already set.") 1734 | sys.exit(1) 1735 | 1736 | if executable_path is None: 1737 | print("Error: Path to executable is required.") 1738 | sys.exit(1) 1739 | 1740 | # Now call run_tests with the collected options 1741 | run_tests(executable_path, udp=udp, tcp=tcp, test_case=test_case) 1742 | --------------------------------------------------------------------------------