diff --git a/tests/test_scraper.py b/tests/test_scraper.py index 0f893be..a0bef40 100644 --- a/tests/test_scraper.py +++ b/tests/test_scraper.py @@ -87,12 +87,6 @@ class Handler(http.server.BaseHTTPRequestHandler): self.answer(False) -def free_port(): - with socket.socket() as s: - s.bind(('127.0.0.1', 0)) - return s.getsockname()[1] - - class Scraper(unittest.TestCase): @classmethod def setUpClass(cls): @@ -134,19 +128,10 @@ class Scraper(unittest.TestCase): (self.root / f'playlists/{DB}.lpl').write_text(json.dumps({'version': '1.5', 'items': items})) def start_server(self, lease=None): - self.port = free_port() env = {**os.environ, 'PS5_SCRAPER_LIBRETRO_BASE': f'http://127.0.0.1:{self.source.server_port}'} if lease: env['PS5_SCRAPER_LEASE_SECONDS'] = str(lease) - self.server = subprocess.Popen([str(self.binary), str(self.root), str(self.port)], env=env, - stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - for _ in range(200): - try: - self.token = json.loads(self.request('GET', '/api/status')[1])['token'] - return - except OSError: - time.sleep(0.02) - raise RuntimeError('the server did not start') + self.server, self.port, self.token = webui_build.start(self.binary, self.root, env) def stop_server(self): self.server.terminate(); self.server.wait(timeout=10) diff --git a/tests/test_scraper_launchbox.py b/tests/test_scraper_launchbox.py index 7948894..4aa9598 100644 --- a/tests/test_scraper_launchbox.py +++ b/tests/test_scraper_launchbox.py @@ -140,18 +140,9 @@ class LaunchBox(unittest.TestCase): self.start_server() def start_server(self): - self.port = base.free_port() env = {**os.environ, 'PS5_SCRAPER_LAUNCHBOX_BASE': f'http://127.0.0.1:{self.source.server_port}', 'PS5_SCRAPER_LIBRETRO_BASE': f'http://127.0.0.1:{self.libretro.server_port}'} - self.server = subprocess.Popen([str(self.binary), str(self.root), str(self.port)], env=env, - stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - for _ in range(200): - try: - self.token = json.loads(self.request('GET', '/api/status')[1])['token'] - return - except OSError: - time.sleep(0.02) - raise RuntimeError('the server did not start') + self.server, self.port, self.token = webui_build.start(self.binary, self.root, env) def tearDown(self): if self.server.poll() is None: diff --git a/tests/test_scraper_screenscraper.py b/tests/test_scraper_screenscraper.py index 837d21b..6a34697 100644 --- a/tests/test_scraper_screenscraper.py +++ b/tests/test_scraper_screenscraper.py @@ -167,20 +167,11 @@ class ScreenScraper(unittest.TestCase): self.start_server() def start_server(self, developer=True): - self.port = base.free_port() env = {**os.environ, 'PS5_SCRAPER_SCREENSCRAPER_BASE': f'http://127.0.0.1:{self.source.server_port}', 'PS5_SCRAPER_LIBRETRO_BASE': f'http://127.0.0.1:{self.libretro.server_port}'} if developer: env.update(PS5_SCRAPER_TEST_DEVID=DEV_ID, PS5_SCRAPER_TEST_DEVPASSWORD=DEV_PASSWORD) - self.server = subprocess.Popen([str(self.binary), str(self.root), str(self.port)], env=env, - stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - for _ in range(200): - try: - self.token = json.loads(self.request('GET', '/api/status')[1])['token'] - return - except OSError: - time.sleep(0.02) - raise RuntimeError('the server did not start') + self.server, self.port, self.token = webui_build.start(self.binary, self.root, env) def tearDown(self): if self.server.poll() is None: diff --git a/tests/test_webui.py b/tests/test_webui.py index 284d5ab..05cda60 100644 --- a/tests/test_webui.py +++ b/tests/test_webui.py @@ -30,18 +30,7 @@ class WebUI(unittest.TestCase): (cls.root / 'config/retroarch.cfg').write_bytes(cls.original) cls.binary = cls.root / 'server' webui_build.build(cls.binary, 'tests/webui_server_main.cpp') - with socket.socket() as s: - s.bind(('127.0.0.1', 0)); cls.port = s.getsockname()[1] - cls.process = subprocess.Popen([str(cls.binary), str(cls.root), str(cls.port)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - for _ in range(100): - try: - with socket.create_connection(('127.0.0.1', cls.port), timeout=.1): break - except OSError: - if cls.process.poll() is not None: raise RuntimeError('HTTP server exited') - time.sleep(.02) - else: raise RuntimeError('HTTP server did not start') - _, _, body = cls.request('GET', '/api/status') - cls.token = json.loads(body)['token'] + cls.process, cls.port, cls.token = webui_build.start(cls.binary, cls.root) @classmethod def tearDownClass(cls): @@ -180,14 +169,8 @@ class WebUI(unittest.TestCase): self.assertEqual(self.request('POST', endpoint, (key + '=chosen').encode(), {'X-RetroArch-Revision': current['revision']})[0], 200) self.process.terminate(); self.process.wait(timeout=5) - self.__class__.process = subprocess.Popen([str(self.binary), str(self.root), str(self.port)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - for _ in range(100): - try: - status, _, body = self.request('GET', '/api/status') - if status == 200: break - except OSError: pass - time.sleep(.02) - self.__class__.token = json.loads(body)['token'] + cls = self.__class__ + cls.process, cls.port, cls.token = webui_build.start(self.binary, self.root) self.assertEqual((self.root / 'config/Fresh Core/Fresh Core.opt').read_text(), 'fresh_resolution = "1"\n') self.assertFalse((self.root / 'config/webui-cores/Fresh Core.opt').exists()) for core in ('PPSSPP', 'dolphin-emu'): @@ -321,15 +304,7 @@ class WebUI(unittest.TestCase): (profile / 'Example Core.opt').write_bytes(original) cls = self.__class__ cls.process.terminate(); cls.process.wait(timeout=5) - cls.process = subprocess.Popen([str(cls.binary), str(cls.root), str(cls.port)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - for _ in range(100): - try: - status, _, body = cls.request('GET', '/api/status') - if status == 200: break - except OSError: pass - time.sleep(.02) - else: self.fail('Server did not restart') - cls.token = json.loads(body)['token'] + cls.process, cls.port, cls.token = webui_build.start(cls.binary, cls.root) self.assertIn(b'example_resolution = "4x"', (profile / 'Example Core.opt').read_bytes()) self.assertIn(b'example_filter = "nearest"', (profile / 'Example Core.opt').read_bytes()) self.assertIn(b'video_vsync = "false"', (profile / 'Example Core.cfg').read_bytes()) diff --git a/tests/test_webui_language.py b/tests/test_webui_language.py index a8c76a8..4f1800b 100644 --- a/tests/test_webui_language.py +++ b/tests/test_webui_language.py @@ -5,10 +5,8 @@ import http.client import json import os import shutil -import socket import subprocess import tempfile -import time import unittest import sys @@ -43,17 +41,7 @@ class Server: subprocess.check_output(['bash', 'tools/build-webui-update.sh', 'host'], cwd=ROOT, text=True) self.binary = root.parent / 'server' webui_build.build(self.binary, 'tests/webui_server_main.cpp', flags=('-O1',)) - with socket.socket() as s: - s.bind(('127.0.0.1', 0)); self.port = s.getsockname()[1] - self.process = subprocess.Popen([str(self.binary), str(root), str(self.port)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - for _ in range(100): - try: - with socket.create_connection(('127.0.0.1', self.port), timeout=.1): break - except OSError: - if self.process.poll() is not None: raise RuntimeError('HTTP server exited') - time.sleep(.02) - self.token = '' - self.token = self.json('GET', '/api/status')[1]['token'] + self.process, self.port, self.token = webui_build.start(self.binary, root) def request(self, method, path, token=None): conn = http.client.HTTPConnection('127.0.0.1', self.port, timeout=5) diff --git a/tests/test_webui_transfer_browser.py b/tests/test_webui_transfer_browser.py index 5e1802a..1217a7c 100644 --- a/tests/test_webui_transfer_browser.py +++ b/tests/test_webui_transfer_browser.py @@ -3,10 +3,8 @@ the native server, then the files on disk compared with what the page made.""" from pathlib import Path import os import shutil -import socket import subprocess import tempfile -import time import unittest import sys @@ -38,11 +36,8 @@ class TransferBrowser(unittest.TestCase): shutil.copytree(ROOT / 'webui', root / 'webui', dirs_exist_ok=True) binary = Path(temp) / 'server' webui_build.build(binary, 'tests/webui_server_main.cpp', flags=('-O1',)) - with socket.socket() as s: - s.bind(('127.0.0.1', 0)); port = s.getsockname()[1] - server = subprocess.Popen([str(binary), str(root), str(port)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + server, port, _ = webui_build.start(binary, root) try: - time.sleep(0.5) try: run = subprocess.run(['node', 'tests/webui_transfer_browser.cjs'], cwd=ROOT, capture_output=True, text=True, timeout=600, env={**os.environ, 'PLAYWRIGHT_PATH': PLAYWRIGHT, 'WEBUI_TEST_URL': f'http://127.0.0.1:{port}'}) diff --git a/tests/webui_build.py b/tests/webui_build.py index 1ff64bf..5e31c0f 100644 --- a/tests/webui_build.py +++ b/tests/webui_build.py @@ -1,7 +1,11 @@ """Builds the WebUI's server for the host (the tests' fixtures): the given main, the server's sources and its libraries, with defines. One list of sources for every test.""" from pathlib import Path +import http.client +import json +import socket import subprocess +import time ROOT = Path(__file__).resolve().parent.parent SOURCES = ['src/webui_ps5.cpp', 'src/webui_transfer.cpp', 'src/webui_update.cpp', 'src/scraper.cpp', @@ -26,3 +30,31 @@ def build(output, main, defines=(), flags=('-O1', '-g')): *[f'-D{d}' for d in defines], main, *SOURCES, *objects, http, update, '-lz', '-lcurl', '-o', str(output)], cwd=ROOT, check=True) return output + + +def start(binary, root, env=None, seconds=8.0): + """Starts a server built above (tests/webui_server_main.cpp) for the title folder root on a + free port, once it answers: its process, port and session token. A port found free can be + taken before the server binds it (another connection gets it as its own port): the server + then exits at once, "Address already in use", and starts again on another.""" + for _ in range(5): + with socket.socket() as probe: + probe.bind(('127.0.0.1', 0)) + port = probe.getsockname()[1] + process = subprocess.Popen([str(binary), str(root), str(port)], env=env, + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + deadline = time.monotonic() + seconds + while process.poll() is None and time.monotonic() < deadline: + try: + connection = http.client.HTTPConnection('127.0.0.1', port, timeout=5) + connection.request('GET', '/api/status') + token = json.loads(connection.getresponse().read())['token'] + connection.close() + return process, port, token + except (OSError, http.client.HTTPException): + time.sleep(0.02) + if process.poll() is None: # running, and no answer: no port problem + process.kill() + process.wait() + break + raise RuntimeError('the server did not start')