Skip to content

Commit

Permalink
Merge pull request #110 from pmxbot/debt/native-namespace
Browse files Browse the repository at this point in the history
Employ native namespace packages for pmxbot and pmxbot.web.
  • Loading branch information
jaraco committed Jun 29, 2023
2 parents 31b0263 + f1102c6 commit e9ff5c9
Show file tree
Hide file tree
Showing 11 changed files with 157 additions and 155 deletions.
1 change: 1 addition & 0 deletions newsfragments/108.feature.rst
@@ -0,0 +1 @@
``pmxbot`` is now a native namespace package and no longer relies on ``pkg_resources`` for that old technique for a namespace package.
1 change: 0 additions & 1 deletion pmxbot/__init__.py

This file was deleted.

4 changes: 3 additions & 1 deletion pytest.ini
@@ -1,6 +1,8 @@
[pytest]
norecursedirs=dist build .tox .eggs
addopts=--doctest-modules
addopts=
--doctest-modules
--import-mode importlib
filterwarnings=
## upstream

Expand Down
1 change: 0 additions & 1 deletion setup.cfg
Expand Up @@ -18,7 +18,6 @@ classifiers =

[options]
packages = find_namespace:
namespace_packages = pmxbot
include_package_data = true
python_requires = >=3.8
install_requires =
Expand Down
148 changes: 0 additions & 148 deletions tests/functional/__init__.py
@@ -1,148 +0,0 @@
import subprocess
import os
import sys
import time
import sqlite3
import datetime
import urllib.parse

import irc.client
import pytest
import tempora.timing

import pmxbot.dictlib


class TestingClient:
"""
A simple client simulating a user other than the pmxbot
"""

def __init__(self, server, port, nickname):
self.reactor = irc.client.Reactor()
self.c = self.reactor.server()
self.c.connect(server, port, nickname)
self.reactor.process_once(0.1)
self.channels = set()

def join(self, channel):
self.c.join(channel)
self.channels.add(channel)

def send_message(self, channel, message):
if channel not in self.channels:
self.join(channel)
self.c.privmsg(channel, message)
time.sleep(0.05)


class PmxbotHarness:
config = pmxbot.dictlib.ConfigDict(
server_port=6668,
bot_nickname='pmxbotTest',
log_channels=['#logged'],
other_channels=['#inane'],
database="sqlite:tests/functional/pmxbot.sqlite",
)

@classmethod
def setup_class(cls):
"""Start an IRC server, launch the bot, and ask it to do stuff"""
path = os.path.dirname(os.path.abspath(__file__))
cls.config_fn = os.path.join(path, 'testconf.yaml')
cls.config.to_yaml(cls.config_fn)
cls.dbfile = urllib.parse.urlparse(cls.config['database']).path
cls.db = sqlite3.connect(cls.dbfile)
env = os.environ.copy()
# copy the current sys.path to PYTHONPATH so subprocesses have access
# to libs pulled by tests_require
env['PYTHONPATH'] = os.pathsep.join(sys.path)
try:
cmd = [sys.executable, '-m', 'irc.server', '-p', '6668', '-l', 'debug']
cls.server = subprocess.Popen(cmd, env=env)
except OSError:
pytest.skip("Unable to launch irc server.")
time.sleep(0.5)
# add './plugins' to the path so we get some pmxbot commands specific
# for testing.
plugins = os.path.join(path, 'plugins')
env['PYTHONPATH'] = os.pathsep.join([plugins, env['PYTHONPATH']])
try:
# Launch pmxbot using Python directly (rather than through
# the console entry point, which can't be properly
# .terminate()d on Windows.
cmd = [sys.executable, '-m', 'pmxbot', cls.config_fn]
cls.bot = subprocess.Popen(cmd, env=env)
except OSError:
pytest.skip("Unable to launch pmxbot (pmxbot must be installed)")
cls.wait_for_tables()
cls.wait_for_output()
if cls.bot.poll() is not None:
pytest.skip("Bot did not start up properly")
cls.client = TestingClient('localhost', 6668, 'testingbot')

@classmethod
def wait_for_output(cls):
"""
Wait for 'Running with config' in cls.bot.output
"""
if cls.bot.poll() is not None:
return
# stubbed
time.sleep(5)

@classmethod
def wait_for_tables(cls, timeout=30):
watch = tempora.timing.Stopwatch()
while watch.split() < datetime.timedelta(seconds=timeout):
try:
cls.check_logs('#check')
return
except Exception:
# short-circuit if the bot has stopped
if cls.bot.poll() is not None:
return
time.sleep(0.2)

@classmethod
def check_logs(cls, channel='', nick='', message=''):
if channel.startswith('#'):
channel = channel[1:]
time.sleep(0.2)
cursor = cls.db.cursor()
query = (
"select * from logs where 1=1"
+ " and channel = :channel" * bool(channel)
+ " and nick = :nick" * bool(nick)
+ " and message = :message" * bool(message)
)
cursor.execute(query, locals())
res = cursor.fetchall()
print(res)
return len(res) >= 1

@classmethod
def teardown_class(cls):
os.remove(cls.config_fn)
if hasattr(cls, 'bot') and not cls.bot.poll():
cls.bot.terminate()
cls.bot.wait()
if hasattr(cls, 'server') and cls.server.poll() is None:
cls.server.terminate()
cls.server.wait()
if hasattr(cls, 'db'):
cls.db.rollback()
cls.db.close()
del cls.db
if hasattr(cls, 'client'):
del cls.client
# wait up to 10 seconds for the file to be removable
for x in range(100):
try:
if os.path.isfile(cls.dbfile):
os.remove(cls.dbfile)
break
except OSError:
time.sleep(0.1)
else:
raise RuntimeError('Could not remove log db', cls.dbfile)
148 changes: 148 additions & 0 deletions tests/functional/harness.py
@@ -0,0 +1,148 @@
import subprocess
import os
import sys
import time
import sqlite3
import datetime
import urllib.parse

import irc.client
import pytest
import tempora.timing

import pmxbot.dictlib


class TestingClient:
"""
A simple client simulating a user other than the pmxbot
"""

def __init__(self, server, port, nickname):
self.reactor = irc.client.Reactor()
self.c = self.reactor.server()
self.c.connect(server, port, nickname)
self.reactor.process_once(0.1)
self.channels = set()

def join(self, channel):
self.c.join(channel)
self.channels.add(channel)

def send_message(self, channel, message):
if channel not in self.channels:
self.join(channel)
self.c.privmsg(channel, message)
time.sleep(0.05)


class PmxbotHarness:
config = pmxbot.dictlib.ConfigDict(
server_port=6668,
bot_nickname='pmxbotTest',
log_channels=['#logged'],
other_channels=['#inane'],
database="sqlite:tests/functional/pmxbot.sqlite",
)

@classmethod
def setup_class(cls):
"""Start an IRC server, launch the bot, and ask it to do stuff"""
path = os.path.dirname(os.path.abspath(__file__))
cls.config_fn = os.path.join(path, 'testconf.yaml')
cls.config.to_yaml(cls.config_fn)
cls.dbfile = urllib.parse.urlparse(cls.config['database']).path
cls.db = sqlite3.connect(cls.dbfile)
env = os.environ.copy()
# copy the current sys.path to PYTHONPATH so subprocesses have access
# to libs pulled by tests_require
env['PYTHONPATH'] = os.pathsep.join(sys.path)
try:
cmd = [sys.executable, '-m', 'irc.server', '-p', '6668', '-l', 'debug']
cls.server = subprocess.Popen(cmd, env=env)
except OSError:
pytest.skip("Unable to launch irc server.")
time.sleep(0.5)
# add './plugins' to the path so we get some pmxbot commands specific
# for testing.
plugins = os.path.join(path, 'plugins')
env['PYTHONPATH'] = os.pathsep.join([plugins, env['PYTHONPATH']])
try:
# Launch pmxbot using Python directly (rather than through
# the console entry point, which can't be properly
# .terminate()d on Windows.
cmd = [sys.executable, '-m', 'pmxbot', cls.config_fn]
cls.bot = subprocess.Popen(cmd, env=env)
except OSError:
pytest.skip("Unable to launch pmxbot (pmxbot must be installed)")
cls.wait_for_tables()
cls.wait_for_output()
if cls.bot.poll() is not None:
pytest.skip("Bot did not start up properly")
cls.client = TestingClient('localhost', 6668, 'testingbot')

@classmethod
def wait_for_output(cls):
"""
Wait for 'Running with config' in cls.bot.output
"""
if cls.bot.poll() is not None:
return
# stubbed
time.sleep(5)

@classmethod
def wait_for_tables(cls, timeout=30):
watch = tempora.timing.Stopwatch()
while watch.split() < datetime.timedelta(seconds=timeout):
try:
cls.check_logs('#check')
return
except Exception:
# short-circuit if the bot has stopped
if cls.bot.poll() is not None:
return
time.sleep(0.2)

@classmethod
def check_logs(cls, channel='', nick='', message=''):
if channel.startswith('#'):
channel = channel[1:]
time.sleep(0.2)
cursor = cls.db.cursor()
query = (
"select * from logs where 1=1"
+ " and channel = :channel" * bool(channel)
+ " and nick = :nick" * bool(nick)
+ " and message = :message" * bool(message)
)
cursor.execute(query, locals())
res = cursor.fetchall()
print(res)
return len(res) >= 1

@classmethod
def teardown_class(cls):
os.remove(cls.config_fn)
if hasattr(cls, 'bot') and not cls.bot.poll():
cls.bot.terminate()
cls.bot.wait()
if hasattr(cls, 'server') and cls.server.poll() is None:
cls.server.terminate()
cls.server.wait()
if hasattr(cls, 'db'):
cls.db.rollback()
cls.db.close()
del cls.db
if hasattr(cls, 'client'):
del cls.client
# wait up to 10 seconds for the file to be removable
for x in range(100):
try:
if os.path.isfile(cls.dbfile):
os.remove(cls.dbfile)
break
except OSError:
time.sleep(0.1)
else:
raise RuntimeError('Could not remove log db', cls.dbfile)
2 changes: 1 addition & 1 deletion tests/functional/test_exceptions.py
@@ -1,6 +1,6 @@
import time

from tests.functional import PmxbotHarness
from .harness import PmxbotHarness


class TestPmxbotExceptions(PmxbotHarness):
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/test_logging.py
@@ -1,7 +1,7 @@
import time
import uuid

from tests.functional import PmxbotHarness
from .harness import PmxbotHarness


class TestPmxbotLog(PmxbotHarness):
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/test_messages.py
@@ -1,6 +1,6 @@
import time

from tests.functional import PmxbotHarness
from .harness import PmxbotHarness


class TestPmxbotMessages(PmxbotHarness):
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/test_regexps.py
@@ -1,6 +1,6 @@
import time

from tests.functional import PmxbotHarness
from .harness import PmxbotHarness


class TestPmxbotRegexp(PmxbotHarness):
Expand Down
1 change: 1 addition & 0 deletions tox.ini
Expand Up @@ -4,6 +4,7 @@ toxworkdir={env:TOX_WORK_DIR:.tox}

[testenv]
deps =
pytest @ git+https://github.com/jaraco/pytest@pmxbot-108
setenv =
PYTHONWARNDEFAULTENCODING = 1
commands =
Expand Down

0 comments on commit e9ff5c9

Please sign in to comment.