From 72012143b21c1ece47a91911c8260dac4b9e7b8e Mon Sep 17 00:00:00 2001 From: Simon Howroyd Date: Sat, 26 Aug 2023 14:49:52 +0100 Subject: [PATCH] Added tests to setup and added the offlineirc for testing --- .coveragerc | 2 + pyproject.toml | 18 +++++- src/twitchirc_drgreengiant/offlineirc.py | 82 ++++++++++++++++++++++++ src/twitchirc_drgreengiant/twitchirc.py | 2 +- 4 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 .coveragerc create mode 100644 src/twitchirc_drgreengiant/offlineirc.py diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..6dca377 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,2 @@ +[run] +omit = run_tests.py, __init__.py \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 3f913e8..fc120d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,10 @@ [build-system] -requires = ["setuptools>=61.0"] +requires = ["pytest", "pytest-cov", "setuptools>=61.0"] build-backend = "setuptools.build_meta" [project] name = "twitchirc_drgreengiant" -version = "2.0.0rc0" +version = "2.0.0" authors = [{ name = "Simon Howroyd", email = "howroydlsu@gmail.com" }] description = "A simple receive only Twitch IRC client" keywords = ["twitch", "irc", "chat"] @@ -21,3 +21,17 @@ classifiers = [ "Homepage" = "https://github.com/howroyd/twitchirc" "Repository" = "https://github.com/howroyd/twitchirc" "Bug Tracker" = "https://github.com/howroyd/twitchirc/issues" + +[tool.setuptools.packages.find] +where = ["src", "tests"] + +[tool.pytest.ini_options] +minversion = "2.0" +python_files = ["tests/test_*.py", "tests/*_test.py"] +addopts = [ + "--cov=.", + "--cov-report=html", + "--cov-report=term-missing", + "--cov-fail-under=95", +] +filterwarnings = "ignore::DeprecationWarning" diff --git a/src/twitchirc_drgreengiant/offlineirc.py b/src/twitchirc_drgreengiant/offlineirc.py new file mode 100644 index 0000000..6e6e76e --- /dev/null +++ b/src/twitchirc_drgreengiant/offlineirc.py @@ -0,0 +1,82 @@ +#!./.venv/bin/python3 +import dataclasses +import multiprocessing as mp +import queue +import sys +from typing import NoReturn, Self + +from . import twitchirc + + +@dataclasses.dataclass(slots=True) +class OfflineIrcThreadArgs: + """Arguments to pass to the offline IRC thread""" + username: str + channel: frozenset[str] + queue: mp.Queue = dataclasses.field(default_factory=mp.Queue) + + +def _offline_irc_thread(args: OfflineIrcThreadArgs) -> NoReturn: + """Thread for offline IRC connection + Rather than reading from the IRC server, we read from stdin + """ + sys.stdin = open(0) + + while True: + userinput = sys.stdin.readline().strip() + + msg = twitchirc.TwitchMessage( + twitchirc.TwitchMessageEnum.PRIVMSG, + args.username, + next(iter(args.channel)), + userinput + ) + + args.queue.put(msg) + + +class OfflineIrc: + """Context manager for offline IRC connection which reads from stdin rather than the IRC server""" + + def __init__(self, channel: frozenset[str], username: str | None = None): + if not channel or channel.issubset(frozenset([""])): + raise twitchirc.TwitchIrcConnectionError("No channels specified") + self._processdata = OfflineIrcThreadArgs( + username=username or "justinfan97339", + channel=channel + ) + self._process: mp.Process | None = None + + @property + def queue(self) -> mp.Queue: + """Returns the queue of messages from stdin""" + return self._processdata.queue + + @staticmethod + def get_message(irc: Self, *, timeout: float = 0.1) -> twitchirc.TwitchMessage | None: + """Returns a message from stdin, or None if no message is available""" + msg: twitchirc.TwitchMessage | None = None + try: + msg = irc.queue.get(timeout=timeout) + if not msg: + raise twitchirc.NoMessageException + except (twitchirc.NoMessageException, queue.Empty): + pass + return msg + + def start(self) -> None: + """Starts the connection to pretend IRC server""" + self._process = mp.Process(target=_offline_irc_thread, args=(self._processdata,)) + self._process.start() + + def stop(self) -> None: + """Forcibly terminate the connection. May deadlock anyone waiting on the queue""" + # FIXME - This is a bit of a hack. We should probably send a message to the thread to tell it to stop + self._process.terminate() if self._process else None + + def __enter__(self): + self.start() + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.stop() diff --git a/src/twitchirc_drgreengiant/twitchirc.py b/src/twitchirc_drgreengiant/twitchirc.py index f12b59b..921e7c5 100644 --- a/src/twitchirc_drgreengiant/twitchirc.py +++ b/src/twitchirc_drgreengiant/twitchirc.py @@ -283,7 +283,7 @@ def get_message(irc: Self, *, timeout: float = 0.1) -> TwitchMessage | None: if __name__ == "__main__": testchannels = frozenset([""]) - #testchannels = frozenset(["drgreengiant"]) + # testchannels = frozenset(["drgreengiant"]) with TwitchIrc(testchannels) as irc: while True: