mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-07-23 20:20:45 -07:00
WebHost: Config option for custom port ranges v2 (#6009)
* Added ability to define custom port ranges the WebHost will use for game servers, instead of pure random. * - Added better fallback to default port range when a custom range fails - Updated config to be clearer * Added ability to define custom port ranges the WebHost will use for game servers, instead of pure random. * - Added better fallback to default port range when a custom range fails - Updated config to be clearer * Updated soft-fail message * Removed dead import from customserver.py * Update requirements.txt Settings requirements to main core branch * fix what reviewers said and add some improvements * remove unused argument * try fixing test with try * use yaml lists instead of string for config * fix value type bug on ephemeral type * reuse sockets with websockets api instead of opening and closing them * add used ports cache and filter used ports when looking for ports * fix port randomizer * Apply suggestions from code review Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com> * fix some reviews * use weights for random port and remove more-itertools * fix net_connections not working on macOS * rename variables and functions * lazy init `get_used_ports` * change `game_ports` to be `tuple` * fix last_used_ports not being updated locally * fix random choices and move game_port conversion into tuple * Apply suggestions from code review Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com> * use a named tuple on parse_game_ports * only use ranges * do it the duck way * this should check all usable ports before failing * fix while loop * add return type to weighted random * Update WebHostLib/customserver.py Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> * simplify tuple conversion check * add tests * reformat file and change `create_random_port_socket` test * add more test cases for parse_game_ports * try to prevent busy-looping on create random port socket when doing test * simplify parse game port tests to one assertListEqual * make the range lesser for port test * reduce range on macOS * Apply suggestions from code review Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> * Update WebHostLib/customserver.py Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com> * remove unused import * Update WebHostLib/customserver.py Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com> * use generator expressions * check for 0-tuple * use some kind of shuffled queue * update tests * refactor new port handling into a class (#1) * change time to monotonic * Update docs/webhost configuration sample.yaml Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> * add psutil 7.2.2 as requirement * Update WebHostLib/requirements.txt Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> --------- Co-authored-by: Lexipherous <jasonnlelong@gmail.com> Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com> Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
import os
|
||||
import unittest
|
||||
from socket import socket as Socket # noqa: N812
|
||||
|
||||
from Utils import is_macos
|
||||
from WebHostLib.customserver import RandomPortSocketCreator
|
||||
|
||||
ci = bool(os.environ.get("CI"))
|
||||
|
||||
|
||||
class TestPortAllocating(unittest.TestCase):
|
||||
def test_parse_game_ports(self) -> None:
|
||||
"""Ensure that game ports with ranges are parsed correctly"""
|
||||
val = RandomPortSocketCreator._parse_game_ports(("1000-2000", "2000-5000", "1000-2000", 20, 40, "20", "0"))
|
||||
|
||||
self.assertCountEqual(val.valid_ports,
|
||||
[*range(1000, 2001), *range(2000, 5001), *range(1000, 2001), 20, 40, 20],
|
||||
"The parsed game ports are not the expected length")
|
||||
self.assertTrue(val.ephemeral_allowed, "The ephemeral allowed flag is not set even though it was passed")
|
||||
|
||||
val = RandomPortSocketCreator._parse_game_ports(())
|
||||
self.assertListEqual(val.valid_ports, [], "Empty list of game port returned something")
|
||||
self.assertFalse(val.ephemeral_allowed, "Empty list returned that ephemeral is allowed")
|
||||
|
||||
val = RandomPortSocketCreator._parse_game_ports((0,))
|
||||
self.assertListEqual(val.valid_ports, [], "Empty list of ranges returned something")
|
||||
self.assertTrue(val.ephemeral_allowed, "List with just 0 is not allowing ephemeral ports")
|
||||
|
||||
val = RandomPortSocketCreator._parse_game_ports((1,))
|
||||
self.assertListEqual(val.valid_ports, [1], "Valid ports doesn't contain the expected values")
|
||||
self.assertFalse(val.ephemeral_allowed, "List with just single port returned that ephemeral is allowed")
|
||||
|
||||
def test_parse_game_port_errors(self) -> None:
|
||||
"""Ensure that game ports with incorrect values raise the expected error"""
|
||||
with self.assertRaises(ValueError, msg="Negative numbers didn't get interpreted as an invalid range"):
|
||||
RandomPortSocketCreator._parse_game_ports(tuple("-50215"))
|
||||
with self.assertRaises(ValueError, msg="Text got interpreted as a valid number"):
|
||||
RandomPortSocketCreator._parse_game_ports(tuple("dwafawg"))
|
||||
with self.assertRaises(
|
||||
ValueError,
|
||||
msg="A range with an extra dash at the end didn't get interpreted as an invalid number because of it's end dash"
|
||||
):
|
||||
RandomPortSocketCreator._parse_game_ports(tuple("20-21215-"))
|
||||
with self.assertRaises(ValueError, msg="Text got interpreted as a valid number for the start of a range"):
|
||||
RandomPortSocketCreator._parse_game_ports(tuple("f-21215"))
|
||||
|
||||
def test_random_port_socket_edge_cases(self) -> None:
|
||||
"""Verify if edge cases on creation of random port socket is working fine"""
|
||||
# Try giving an empty tuple and fail over it
|
||||
creator = RandomPortSocketCreator(())
|
||||
with self.assertRaises(OSError) as err:
|
||||
creator.create("127.0.0.1")
|
||||
self.assertEqual(err.exception.errno, 98, "Raised an unexpected error code")
|
||||
self.assertEqual(err.exception.strerror, "No available ports", "Raised an unexpected error string")
|
||||
|
||||
# Try only having ephemeral ports enabled
|
||||
creator = RandomPortSocketCreator(("0",))
|
||||
try:
|
||||
creator.create("127.0.0.1").close()
|
||||
except OSError as err:
|
||||
self.assertEqual(err.errno, 98, "Raised an unexpected error code")
|
||||
# If it returns our error string that means something is wrong with our code
|
||||
self.assertNotEqual(err.strerror, "No available ports",
|
||||
"Raised an unexpected error string")
|
||||
|
||||
@unittest.skipUnless(ci, "can't guarantee free ports outside of CI")
|
||||
def test_random_port_socket(self) -> None:
|
||||
"""Verify if returned sockets use the correct port ranges"""
|
||||
creator = RandomPortSocketCreator(("8080-8085",))
|
||||
sockets: list[Socket] = []
|
||||
for _ in range(6):
|
||||
socket = creator.create("127.0.0.1")
|
||||
sockets.append(socket)
|
||||
_, port = socket.getsockname()
|
||||
self.assertIn(port, range(8080, 8086), "Port of socket was not inside the expected range")
|
||||
for s in sockets:
|
||||
s.close()
|
||||
|
||||
sockets.clear()
|
||||
creator = RandomPortSocketCreator(("30000-65535",))
|
||||
length = 5_000 if is_macos else (30_000 - len(creator._get_used_ports()))
|
||||
for _ in range(length):
|
||||
socket = creator.create("127.0.0.1")
|
||||
sockets.append(socket)
|
||||
_, port = socket.getsockname()
|
||||
self.assertIn(port, range(30_000, 65536), "Port of socket was not inside the expected range")
|
||||
|
||||
for s in sockets:
|
||||
s.close()
|
||||
Reference in New Issue
Block a user