Skip to content
This repository has been archived by the owner on Aug 26, 2021. It is now read-only.

Commit

Permalink
Add documentation inline, generate the documentation, host somewhere (#…
Browse files Browse the repository at this point in the history
…21)

* Need to prod github into letting me make a PR

* Initialized the sphinx documentation, currently ignoring the build directory

* Added documentation for `__init__.py`'s methods

Added some docstrings, setup the rst to autogenerate based on those docstrings

* Prepped the docs files for the other files

Still have to implement autodoc and improve the documentation for these

* Heavily improved the documentation for bot.py, bot.rst bow autogenerates the correct documentation

I also made some very small changes to some other files and to the sphinx config

* Documented collector.py, got intersphinx working

* Added a dev requirements, added a few more pages, added the toctree, 

Also fixed some issues in the docstrings

* Makefile is now system-agnostic (needs active venv)

* slowly and steadily, documentation is coming along

* Removed 4 TODOs, details below

__init__.py ~124: Made issue #23 covering the TODO
__init__.py ~145: Fixed small error in intersphinx link in `collector.py`
bot.py ~72: Made issue #22 covering the TODO
bot.py ~102: Made issue #24, should be ez to fix

* implement timeout in DiscordInteractiveInterface. Just one line.

* Got quickstart done. finally getting the hang of rst syntax

* prettier and clearer.

* Quickstart looks great! Added a bit, tried to make the interface docs more clear

I'm writing this on a long plane ride (again lol) so this might mostly be trash, I will try to review this again when I get back to internet and am less of a tired mess. Really great job on the quickstart! I might just leave this for you to do from now on though since you have a batter grasp on rst than me, if you're fine with that, we can chat on discord about it if you want.

* Working on creating ci testing for the documentation

* Updated travis.yml to include the sphinx test, let's see how this goes

* Fixing issues in travis.yml

* Pulled the remaining todos, they were not really necessary (thanks tired plane me)

* Remove extraneous words and stuff. Let me know if there are any issues with this, if not I'll do the same sort of thing with all/most of the other pages in the coming days.
  • Loading branch information
Cobular authored Sep 20, 2019
1 parent c1d8de0 commit 28ca659
Show file tree
Hide file tree
Showing 23 changed files with 1,574 additions and 113 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,7 @@ fabric.properties
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
\.idea/sonarlint/

fkjdfkasdfkh\.txt

docs/build/
5 changes: 5 additions & 0 deletions .idea/codeStyles/codeStyleConfig.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 14 additions & 6 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
dist: xenial
language: python
python: '3.7'
python: "3.7"
install:
- pip install -r requirements.txt
- pip install -r requirements-dev.txt
- pip install .
env:
- TARGET_NAME=DistestUnitTestTarget
matrix:
include:
- name: "library_test"
python: "3.7"
env:
- TARGET_NAME=DistestUnitTestTarget
- SCRIPT_NAME="run_tests.sh"
- name: "documentation_test"
python: "3.7"
env: SCRIPT_NAME="test_sphinx.sh"
script:
- "./run_tests.sh"
- ./$SCRIPT_NAME
deploy:
provider: pypi
user: Cobular
password:
secure: b5+J4MyyBe789vl0ARRPALtpLMfdN5nZTUG4aESv38DhAd4m6Dpz1ELB6qyC90/tYsGDkFqf3muW4MgKtp1PTvC+Do0IG0MDEw1S0O8cvHP/6V/bQt/8TxeBWzvo19zzkHWV7ujW9ivug5wYMB8+yLQbhYAQR9SKD9LIvmuXB3GrGuc04FxYb328zCqzavA2HfisfOBGeG4d9P2Y8pEQGo8nHe9O/Ey9oBL3I7o4aaA4QXNXO1RPSDrjV/4nwdAvU+VnkZXA/TwkneFnWf9CbJ0zxxbn+nQxKo3dUfSvfDeHqWlWaquelkVmlacoIlR2UM0/xMrYDOdSTHDoquGDODS9jOt5uJzGr9NH0Nbn/FFFrlbE4+3wuiIM0RPHvdv5Z31d3cJDiurCW8qnV1b83yay94n9N7eeJyuPknEtoWGKgEUp8BjtovDmMydG/WbcPb8tQ4bs2nLn/OPUiIgF5tFXnpnTQbMeJBCp+wIVe7tbclM1RCcdLCSeVquroRTgJKGk/i/WNpm4adi36OxCaXcmurIjqWNFb0pPQL5I0txOnLZ9sNeT4Pzy3ujkc35qqJsgVt5LEuztTchpYtUpbF4eSEjYD85RcrewPmEK3A20d8xEXdFu2r//sXd8BCwJA5UJJOXPLxISa+WTEataZiKixE836VhIEg9bq3qkOoE=
on:
branch: master
branch: master
37 changes: 26 additions & 11 deletions distest/__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
""" Distest is a small library designed to allow you to make discord bots to test other bots.
This is the main file, and contains the code that is directly involved in running the bot
and interacting with the command line, including the classes for bot types and the two interfaces.
"""

import argparse
import sys

from .bot import DiscordInteractiveInterface, DiscordCliInterface
from .collector import TestCollector


def run_dtest_bot(sysargs, test_collector: TestCollector, timeout=5):
def run_dtest_bot(sysargs, test_collector, timeout=5):
""" This is the function you will call in your test suite's ``if __name__ == "__main__":`` statement to
get the bot started.
:param list sysargs: The list returned by ``sys.argv``, this function parses it and will handle errors in format
:param TestCollector test_collector: The :ref:`TestCollector` that has been used to decorate the tests
:param int timeout: An optional parameter to override the amount of time to wait for responses before failing
tests. Defaults to 5 seconds.
"""
from distest.validate_discord_token import token_arg

all_run_options = ["all"]
Expand Down Expand Up @@ -108,22 +110,35 @@ def run_dtest_bot(sysargs, test_collector: TestCollector, timeout=5):


def run_interactive_bot(target_name, token, test_collector, timeout=5):
bot = DiscordInteractiveInterface(target_name, test_collector)
""" Run the bot in interactive mode.
Relies on :py:func:`run_dtest_bot` to parse the command line arguments and pass them here.
Not really meant to be called by the user.
:param str target_name: The display name of the bot we are testing.
:param str token: The tester's token, used to log in.
:param TestCollector test_collector: The collector that gathered our tests.
:param int timeout: The amount of time to wait for responses before failing tests.
"""

bot = DiscordInteractiveInterface(target_name, test_collector, timeout)
bot.run(token) # Starts the bot


def run_command_line_bot(target, token, tests, channel_id, stats, collector, timeout):
""" Start the bot in command-line mode. The program will exit 1 if any of the tests failed.
Relies on :py:func:`run_dtest_bot` to parse the command line arguments and pass them here.
Not really meant to be called by the user.
:param str target: The display name of the bot we are testing.
:param str token: The tester's token, used to log in.
:param str tests: List of tests to run.
:param int channel_id: The ID of the channel in which to run the tests.
:param bool stats: Determines whether or not to display stats after run.
:param TestCollector collector: The ``TestCollector`` that gathered our tests.
:param TestCollector collector: The collector that gathered our tests.
:param int timeout: The amount of time to wait for responses before failing tests.
:rtype: None
"""
bot = DiscordCliInterface(target, collector, tests, channel_id, stats, timeout)
failed = bot.run(token) # returns True if a test failed
sys.exit(1 if failed else 0)
sys.exit(1 if failed else 0) # Calls sys.exit based on the state of `failed`
111 changes: 82 additions & 29 deletions distest/bot.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
""" Contains the discord clients used to run tests.
:py:class:`DiscordBot` contains the logic for running tests and finding the target bot
:py:class:`DiscordInteractiveInterface` is a subclass of :py:class:`DiscordBot` and contains the logic to handle
commands sent from discord to run tests, display stats, and more
:py:class:`DiscordCliInterface` is a subclass of :py:class:`DiscordInteractiveInterface` and simply contains logic to
start the bot when it wakes up
"""
import discord

from .interface import TestResult, Test, TestInterface
Expand All @@ -15,24 +25,43 @@

class DiscordBot(discord.Client):
""" Discord bot used to run tests.
This class by itself does not provide any useful methods for human interaction.
This class by itself does not provide any useful methods for human interaction,
and is just used as a superclass of the two interfaces,
:py:class:`DiscordInteractiveInterface` and :py:class:`DiscordCliInterface`
:param str target_name: The name of the target bot, used to ensure that the target user is actually
present in the server. Good for checking for typos or other simple mistakes.
"""

def __init__(self, target_name):
super().__init__()
self._target_name = target_name.lower()

def _find_target(self, server: discord.Guild) -> discord.Member:
""" Confirms that the target user is actually present in the specified guild
:param discord.Guild server: The ``discord.Guild()`` object of the guild
to look for the target user in
:rtype: discord.Member
"""
for member in server.members:
if self._target_name in member.name.lower():
return member
raise KeyError("Could not find memory with name {}".format(self._target_name))
raise KeyError("Could not find member with name {}".format(self._target_name))

async def run_test(
self, test: Test, channel: discord.TextChannel, stop_error=False
) -> TestResult:
""" Run a single test in a given channel.
Updates the test with the result, and also returns it.
Updates the test with the result and returns it
:param Test test: The :py:class:`Test` that is to be run
:param discord.TextChannel channel: The
:param stop_error: Weather or not to stop the program on error. Not currently in use.
:return: Result of the test
:rtype: TestResult
"""
test_interface = TestInterface(self, channel, self._find_target(channel.guild))
try:
Expand All @@ -48,13 +77,14 @@ async def run_test(


class DiscordInteractiveInterface(DiscordBot):
"""
A variant of the discord bot which supports additional commands in discord
to allow a human to run the tests manually. Does NOT support CLI commands
""" A variant of the discord bot which commands sent in discord to allow
a human to run the tests manually.
Does NOT support CLI arguments
:param str target_name: The name of the bot to target (Username, no discriminator)
:param TestCollector collector: The instance of Test Collector that contains the tests to run
:param int timeout: The amount of time to wait for responses before failing tests.
"""

def __init__(self, target_name, collector: TestCollector, timeout=5):
Expand All @@ -66,15 +96,23 @@ def __init__(self, target_name, collector: TestCollector, timeout=5):
async def _run_by_predicate(self, channel, predicate=lambda test: True):
""" Iterate through ``_tests`` and run any test for which ``predicate`` returns True
:param discord.TextChannel channel: The channel to run the test in.
:param function predicate: The check a test must pass to be run.
:param discord.TextChannel channel: The channel to run the test in.
:param function predicate: The check a test must pass to be run.
"""
for test in self._tests:
if predicate(test):
await self.run_test(test, channel, stop_error=True)

async def _build_stats(self, tests) -> str:
""" Helper function for constructing the stat display based on test status"""
""" Helper function for constructing the stat display based on test status.
Iterate over each test in ``tests`` and creates a string (``response``) based
on the result property of each ``Test``
:param list[Test] tests: The list of tests used to create the stats
:return: Ready-to-send string congaing the results of the tests, including
discord markdown
:rtype: str
"""
response = "```\n"
longest_name = max(map(lambda t: len(t.name), tests))
for test in tests:
Expand All @@ -94,18 +132,29 @@ async def _build_stats(self, tests) -> str:
return response

async def _display_stats(self, channel: discord.TextChannel):
"""Display the status of the various tests."""
""" Display the status of the various tests. Just a send wrapper for
:py:func:`_build_stats`
"""
await channel.send(await self._build_stats(self._tests))

async def on_ready(self):
""" Report when the bot is ready for use """
""" Report when the bot is ready for use and report the available tests
to the console
"""
print("Started distest bot.")
print("Available tests are:")
for test in self._tests:
print(" {}".format(test.name))

async def on_message(self, message: discord.Message):
""" Handle an incoming message """
""" Handle an incoming message, see :py:func:`discord.event.on_message` for
event reference.
Parse a message, can ignore it or parse the message as a command and
run some tests or do one of the alternate functions (stats, list, or help)
:param discord.Message message: The message being recieved, passed by discord.py
"""
if message.author == self.user:
return
if not isinstance(message.channel, (discord.DMChannel, discord.GroupChannel)):
Expand All @@ -120,9 +169,11 @@ async def on_message(self, message: discord.Message):

async def run_tests(self, channel: discord.TextChannel, name: str):
""" Helper function for choosing and running an appropriate suite of tests
Makes sure only tests that still need to be run are run, also prints
to the console when a test is run
:param discord.TextChannel channel: The channel in which to run the tests
:param str name: Selector string used to determine what category of test to run
:param discord.TextChannel channel: The channel in which to run the tests
:param str name: Selector string used to determine what category of test to run
"""
print("Running: ", name)
if name == "all":
Expand All @@ -147,35 +198,37 @@ class DiscordCliInterface(DiscordInteractiveInterface):
""" A variant of the discord bot which is designed to be run off command line arguments.
:param str target_name: The name of the bot to target (Username, no discriminator)
:param TestCollector collector: The instance of Test Collector that contains the tests to run
:param TestCollector collector: The instance of Test Collector that contains the
tests to run
:param str test: The name of the test option (all, specific test, etc)
:param int channel_id: The ID of the channel to run the bot in
:param bool stats: If true, run in stats mode. TODO: See if this is actually useful
:param bool stats: If true, run in hstats mode.
"""

def __init__(
self,
target_name,
collector: TestCollector,
test: str,
channel_id: int,
stats: bool,
timeout: int,
):
def __init__(self, target_name, collector, test, channel_id, stats, timeout):
super().__init__(target_name, collector, timeout)
self._test_to_run = test
self._channel_id = channel_id
self._stats = stats
self._channel = None

# override of the default run() that returns failure state after completion.
def run(self, token):
#
def run(self, token) -> int:
""" Override of the default run() that returns failure state after completion.
Allows the failure to cascade back up until it is processed into an exit code by
:py:func:`run_command_line_bot`
:param str token: The tester bot token
:return: Returns 1 if the any test failed, otherwise returns zero.
:rtype: int
"""
super().run(token)
return self.failure

async def on_ready(self):
""" For CLI, the bot should start testing as soon as its ready, and exit when it is done.
Therefore, this ``on_ready`` does both.
""" Run all the tests sequentially when the bot becomes awake and exit when the
tests finish. The CLI should run all by itself without prompting, and this allows it
to behave that way.
"""
self._channel = self.get_channel(self._channel_id)
print("Started distest bot.")
Expand Down
Loading

0 comments on commit 28ca659

Please sign in to comment.