Skip to content

Commit

Permalink
Add tests for Sable's postgresql chathistory backend
Browse files Browse the repository at this point in the history
  • Loading branch information
progval committed Oct 27, 2024
1 parent c31aaf4 commit b274cad
Show file tree
Hide file tree
Showing 7 changed files with 277 additions and 23 deletions.
29 changes: 15 additions & 14 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,71 +8,72 @@ PYTEST_ARGS ?=
EXTRA_SELECTORS ?=

BAHAMUT_SELECTORS := \
not Ergo \
not implementation-specific \
and not deprecated \
and not strict \
and not IRCv3 \
$(EXTRA_SELECTORS)

CHARYBDIS_SELECTORS := \
not Ergo \
not implementation-specific \
and not deprecated \
and not strict \
$(EXTRA_SELECTORS)

ERGO_SELECTORS := \
(Ergo or not implementation-specific) \
not deprecated \
$(EXTRA_SELECTORS)

HYBRID_SELECTORS := \
not Ergo \
not implementation-specific \
and not deprecated \
$(EXTRA_SELECTORS)

INSPIRCD_SELECTORS := \
not Ergo \
not implementation-specific \
and not deprecated \
and not strict \
$(EXTRA_SELECTORS)

IRCU2_SELECTORS := \
not Ergo \
not implementation-specific \
and not deprecated \
and not strict \
$(EXTRA_SELECTORS)

NEFARIOUS_SELECTORS := \
not Ergo \
not implementation-specific \
and not deprecated \
and not strict \
$(EXTRA_SELECTORS)

SNIRCD_SELECTORS := \
not Ergo \
not implementation-specific \
and not deprecated \
and not strict \
$(EXTRA_SELECTORS)

IRC2_SELECTORS := \
not Ergo \
not implementation-specific \
and not deprecated \
and not strict \
$(EXTRA_SELECTORS)

MAMMON_SELECTORS := \
not Ergo \
not implementation-specific \
and not deprecated \
and not strict \
$(EXTRA_SELECTORS)

NGIRCD_SELECTORS := \
not Ergo \
not implementation-specific \
and not deprecated \
and not strict \
$(EXTRA_SELECTORS)

PLEXUS4_SELECTORS := \
not Ergo \
not implementation-specific \
and not deprecated \
$(EXTRA_SELECTORS)

Expand All @@ -87,7 +88,7 @@ LIMNORIA_SELECTORS := \
# Tests marked with private_chathistory can't pass because Sable does not implement CHATHISTORY for DMs

SABLE_SELECTORS := \
not Ergo \
(Sable or not implementation-specific) \
and not deprecated \
and not strict \
and not arbitrary_client_tags \
Expand All @@ -97,7 +98,7 @@ SABLE_SELECTORS := \
$(EXTRA_SELECTORS)

SOLANUM_SELECTORS := \
not Ergo \
not implementation-specific \
and not deprecated \
and not strict \
$(EXTRA_SELECTORS)
Expand All @@ -119,7 +120,7 @@ THELOUNGE_SELECTORS := \
# Tests marked with private_chathistory can't pass because Unreal does not implement CHATHISTORY for DMs

UNREALIRCD_SELECTORS := \
not Ergo \
not implementation-specific \
and not deprecated \
and not strict \
and not arbitrary_client_tags \
Expand Down
3 changes: 3 additions & 0 deletions irctest/basecontrollers.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ class TestCaseControllerConfig:
This should be used as little as possible, using the other attributes instead;
as they are work with any controller."""

sable_history_server: bool = False
"""Whether to start Sable's long-term history server"""


class _BaseController:
"""Base class for software controllers.
Expand Down
10 changes: 8 additions & 2 deletions irctest/cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -842,16 +842,22 @@ def mark_services(cls: TClass) -> TClass:
def mark_specifications(
*specifications_str: str, deprecated: bool = False, strict: bool = False
) -> Callable[[TCallable], TCallable]:
specifications = frozenset(
specifications = {
Specifications.from_name(s) if isinstance(s, str) else s
for s in specifications_str
)
}
if None in specifications:
raise ValueError("Invalid set of specifications: {}".format(specifications))

is_implementation_specific = all(
spec.is_implementation_specific() for spec in specifications
)

def decorator(f: TCallable) -> TCallable:
for specification in specifications:
f = getattr(pytest.mark, specification.value)(f)
if is_implementation_specific:
f = getattr(pytest.mark, "implementation-specific")(f)
if strict:
f = pytest.mark.strict(f)
if deprecated:
Expand Down
129 changes: 125 additions & 4 deletions irctest/controllers/sable.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import subprocess
import tempfile
import time
from typing import Optional, Type
from typing import Optional, Sequence, Type

from irctest.basecontrollers import (
BaseServerController,
Expand Down Expand Up @@ -85,7 +85,7 @@ def certs_dir() -> Path:
certs_dir = tempfile.TemporaryDirectory()
(Path(certs_dir.name) / "gen_certs.sh").write_text(GEN_CERTS)
subprocess.run(
["bash", "gen_certs.sh", "My.Little.Server", "My.Little.Services"],
["bash", "gen_certs.sh", "My.Little.Server", "My.Little.History", "My.Little.Services"],
cwd=certs_dir.name,
check=True,
)
Expand All @@ -99,6 +99,7 @@ def certs_dir() -> Path:
"ca_file": "%(certs_dir)s/ca_cert.pem",
"peers": [
{ "name": "My.Little.History", "address": "%(history_hostname)s:%(history_port)s", "fingerprint": "%(history_cert_sha1)s" },
{ "name": "My.Little.Services", "address": "%(services_hostname)s:%(services_port)s", "fingerprint": "%(services_cert_sha1)s" },
{ "name": "My.Little.Server", "address": "%(server1_hostname)s:%(server1_port)s", "fingerprint": "%(server1_cert_sha1)s" }
]
Expand All @@ -107,7 +108,7 @@ def certs_dir() -> Path:

NETWORK_CONFIG_CONFIG = """
{
"object_expiry": 300,
"object_expiry": 60, // 1 minute
"opers": [
{
Expand Down Expand Up @@ -219,6 +220,58 @@ def certs_dir() -> Path:
}
"""

HISTORY_SERVER_CONFIG = """
{
"server_id": 50,
"server_name": "My.Little.History",
"management": {
"address": "%(history_management_hostname)s:%(history_management_port)s",
"client_ca": "%(certs_dir)s/ca_cert.pem",
"authorised_fingerprints": [
{ "name": "user1", "fingerprint": "435bc6db9f22e84ba5d9652432154617c9509370" }
]
},
"server": {
"database": "%(history_db_url)s",
"auto_run_migrations": true,
},
"event_log": {
"event_expiry": 300, // five minutes, for local testing
},
"tls_config": {
"key_file": "%(certs_dir)s/My.Little.History.key",
"cert_file": "%(certs_dir)s/My.Little.History.pem"
},
"node_config": {
"listen_addr": "%(history_hostname)s:%(history_port)s",
"cert_file": "%(certs_dir)s/My.Little.History.pem",
"key_file": "%(certs_dir)s/My.Little.History.key"
},
"log": {
"dir": "log/services/",
"module-levels": {
"": "debug",
"sable_history": "trace",
},
"targets": [
{
"target": "stdout",
"level": "trace",
"modules": [ "sable_history" ]
}
]
}
}
"""

SERVICES_CONFIG = """
{
"server_id": 99,
Expand Down Expand Up @@ -348,10 +401,11 @@ def run(

(server1_hostname, server1_port) = self.get_hostname_and_port()
(services_hostname, services_port) = self.get_hostname_and_port()
(history_hostname, history_port) = self.get_hostname_and_port()

# Sable requires inbound connections to match the configured hostname,
# so we can't configure 0.0.0.0
server1_hostname = services_hostname = "127.0.0.1"
server1_hostname = history_hostname = services_hostname = "127.0.0.1"

(
server1_management_hostname,
Expand All @@ -361,6 +415,10 @@ def run(
services_management_hostname,
services_management_port,
) = self.get_hostname_and_port()
(
history_management_hostname,
history_management_port,
) = self.get_hostname_and_port()

self.template_vars = dict(
certs_dir=certs_dir(),
Expand All @@ -381,6 +439,13 @@ def run(
services_management_hostname=services_management_hostname,
services_management_port=services_management_port,
services_alias_users=SERVICES_ALIAS_USERS if run_services else "",
history_hostname=history_hostname,
history_port=history_port,
history_cert_sha1=(certs_dir() / "My.Little.History.pem.sha1")
.read_text()
.strip(),
history_management_hostname=history_management_hostname,
history_management_port=history_management_port,
)

with self.open_file("configs/network.conf") as fd:
Expand Down Expand Up @@ -416,12 +481,22 @@ def run(

if run_services:
self.services_controller = SableServicesController(self.test_config, self)
self.services_controller.faketime_cmd = faketime_cmd
self.services_controller.run(
protocol="sable",
server_hostname=services_hostname,
server_port=services_port,
)

if self.test_config.sable_history_server:
self.history_controller = SableHistoryController(self.test_config, self)
self.history_controller.faketime_cmd = faketime_cmd
self.history_controller.run(
protocol="sable",
server_hostname=history_hostname,
server_port=history_port,
)

def kill_proc(self) -> None:
os.killpg(self.pgroup_id, signal.SIGKILL)
super().kill_proc()
Expand Down Expand Up @@ -470,6 +545,8 @@ class SableServicesController(BaseServicesController):
server_controller: SableController
software_name = "Sable Services"

faketime_cmd: Sequence[str]

def run(self, protocol: str, server_hostname: str, server_port: int) -> None:
assert protocol == "sable"
assert self.server_controller.directory is not None
Expand All @@ -479,6 +556,7 @@ def run(self, protocol: str, server_hostname: str, server_port: int) -> None:

self.proc = self.execute(
[
*self.faketime_cmd,
"sable_services",
"--foreground",
"--server-conf",
Expand All @@ -497,5 +575,48 @@ def kill_proc(self) -> None:
super().kill_proc()


class SableHistoryController(BaseServicesController):
server_controller: SableController
software_name = "Sable History Server"
faketime_cmd: Sequence[str]

def run(self, protocol: str, server_hostname: str, server_port: int) -> None:
assert protocol == "sable"
assert self.server_controller.directory is not None
history_db_url=os.environ.get("PIFPAF_POSTGRESQL_URL") or os.environ.get("IRCTEST_POSTGRESQL_URL")
assert history_db_url, (
"Cannot find a postgresql database to use as backend for sable_history. "
"Either set the IRCTEST_POSTGRESQL_URL env var to a libpq URL, or "
"run `pip3 install pifpaf` and wrap irctest in a pifpaf call (ie. "
"pifpaf run postgresql -- pytest --controller=irctest.controllers.sable ...)"
)

with self.server_controller.open_file("configs/history_server.conf") as fd:
fd.write(HISTORY_SERVER_CONFIG % {
**self.server_controller.template_vars,
"history_db_url": history_db_url,
})

self.proc = self.execute(
[
*self.faketime_cmd,
"sable_history",
"--foreground",
"--server-conf",
self.server_controller.directory / "configs/history_server.conf",
"--network-conf",
self.server_controller.directory / "configs/network.conf",
],
cwd=self.server_controller.directory,
preexec_fn=os.setsid,
env={"RUST_BACKTRACE": "1", **os.environ},
)
self.pgroup_id = os.getpgid(self.proc.pid)

def kill_proc(self) -> None:
os.killpg(self.pgroup_id, signal.SIGKILL)
super().kill_proc()


def get_irctest_controller_class() -> Type[SableController]:
return SableController
Loading

0 comments on commit b274cad

Please sign in to comment.