# mypy: disable-error-code="unreachable" import functools import http.server import logging import os import pathlib import subprocess import sys import tempfile import threading from typing import Any from typing import cast from typing import TYPE_CHECKING import unittest import libtorrent as lt from . import lib from . import tdummy if TYPE_CHECKING: from libtorrent import _Entry # import fails on windows if os.name != "nt": import pty class Handler(http.server.BaseHTTPRequestHandler): def __init__(self, *args: Any, serve_data: bytes, **kwargs: Any) -> None: self.serve_data = serve_data super().__init__(*args, **kwargs) def do_GET(self) -> None: logging.info("%s", self.requestline) self.send_response(200) self.send_header("content-length", str(len(self.serve_data))) self.end_headers() self.wfile.write(self.serve_data) class TestClient(unittest.TestCase): script_path: pathlib.Path tempdir_path: pathlib.Path torrent_path: pathlib.Path pty_master: Any pty_slave: Any torrent: tdummy.Torrent def setUp(self) -> None: if not sys.platform.startswith("linux"): return # path relative to this file self.script_path = pathlib.Path(__file__).parent / ".." / "client.py" self.tempdir = tempfile.TemporaryDirectory() self.tempdir_path = pathlib.Path(self.tempdir.name) self.torrent = tdummy.get_default() # set up a web seed to serve dummy torrent data self.server = http.server.HTTPServer( ("127.0.0.1", 0), functools.partial(Handler, serve_data=self.torrent.data) ) addr = self.server.server_address[0] port = self.server.server_address[1] self.server_thread = threading.Thread(target=self.server.serve_forever) self.server_thread.start() # construct a .torrent to point to our web seed tdict = dict(self.torrent.dict) tdict[b"url-list"] = b"".join( [b"http://", str(addr).encode(), b":", str(port).encode()] ) self.tdict = cast("_Entry", tdict) self.torrent_path = self.tempdir_path / "dummy.torrent" self.torrent_path.write_bytes(lt.bencode(self.tdict)) self.pty_master, self.pty_slave = pty.openpty() def tearDown(self) -> None: if sys.platform != "linux": return self.server.shutdown() self.server.server_close() self.tempdir.cleanup() os.close(self.pty_master) os.close(self.pty_slave) @unittest.skipIf(sys.platform != "linux", "pty control only works on linux") def test_download_from_web_seed(self) -> None: proc = subprocess.Popen( [ sys.executable, str(self.script_path), "--port", "0", "--listen-interface", "127.0.0.1", str(self.torrent_path), ], cwd=self.tempdir_path, stdin=self.pty_slave, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, universal_newlines=True, ) try: # wait for the file to be downloaded from the web seed file_path = self.tempdir_path / os.fsdecode(self.torrent.files[0].path) for _ in lib.loop_until_timeout(15): try: if file_path.read_bytes() == self.torrent.data: break except FileNotFoundError: pass # send a 'q' command os.write(self.pty_master, b"q") # process should terminate on its own returncode = proc.wait(15) finally: proc.kill() # process should complete without errors or warnings self.assertEqual(returncode, 0) assert proc.stderr is not None # helps mypy self.assertEqual(proc.stderr.read(), "") proc.stderr.close() # fastresume should be written fastresume = pathlib.Path(f"{file_path}.fastresume") lt.bdecode(fastresume.read_bytes())