Files
titlebot-ng-ng/test_launcher.py
T

115 lines
4.9 KiB
Python

import json
import os
from pathlib import Path
import shutil
import signal
import subprocess
import sys
import tempfile
import time
import unittest
class LauncherTests(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.root = Path(self.temp.name)
shutil.copy(Path(__file__).with_name("run_all.sh"), self.root)
(self.root / "configs").mkdir()
(self.root / ".venv/bin").mkdir(parents=True)
(self.root / ".venv/bin/activate").write_text("")
self.bin = self.root / "bin"
self.bin.mkdir()
self.executable(self.bin / "go", "#!/bin/sh\ntouch unexpected-go-invocation\nexit 1\n")
self.executable(self.bin / "pgrep", "#!/bin/sh\nexit 1\n")
self.executable(self.bin / "pkill", "#!/bin/sh\nexit 0\n")
self.executable(self.bin / "sleep", "#!/bin/sh\n/bin/sleep 0.02\n")
self.executable(self.bin / "python3", f'#!/bin/sh\nexec "{sys.executable}" "$@"\n')
for name in ("scraper", "post_results"):
(self.root / f"{name}.py").write_text(
"import json, pathlib, sys, time\n"
"name = pathlib.Path(sys.argv[0]).stem\n"
"config = pathlib.Path(sys.argv[1]).name\n"
"pathlib.Path(f'started-{name}-{config}.json').write_text(json.dumps(sys.argv[1:]))\n"
"if config == 'broken room.yaml': sys.exit(1)\n"
"time.sleep(300)\n"
)
def executable(self, path, content):
path.write_text(content)
path.chmod(0o755)
def launch(self):
output = open(self.root / "launcher.log", "w+")
self.addCleanup(output.close)
env = dict(os.environ, PATH=str(self.bin) + os.pathsep + os.environ["PATH"])
process = subprocess.Popen(
["bash", str(self.root / "run_all.sh")], env=env,
stdout=output, stderr=subprocess.STDOUT, start_new_session=True,
)
def stop():
# All processes belong to this isolated test session.
try:
os.killpg(process.pid, signal.SIGTERM)
except ProcessLookupError:
pass
try:
process.wait(timeout=5)
except subprocess.TimeoutExpired:
os.killpg(process.pid, signal.SIGKILL)
process.wait(timeout=5)
self.addCleanup(stop)
return process
def test_each_config_launches_independently_without_config_yaml(self):
configs = ("main room.yaml", "example-room.yml", "broken room.yaml")
for name in (*configs, "config_example.yaml", "disabled.yaml.hold"):
(self.root / "configs" / name).write_text("room_id: test\n")
pid_dir = self.root / ".pids"
pid_dir.mkdir()
(pid_dir / "retired.pid").write_text("999999999")
(pid_dir / "retired.cmd").write_text("touch unexpected-service-started")
process = self.launch()
deadline = time.monotonic() + 15
while time.monotonic() < deadline:
if "Service startup complete" in (self.root / "launcher.log").read_text():
break
if process.poll() is not None:
self.fail((self.root / "launcher.log").read_text())
time.sleep(0.05)
else:
self.fail((self.root / "launcher.log").read_text())
self.assertEqual(len(list(self.root.glob("started-*.json"))), 6)
for config in configs:
for service in ("scraper", "post_results"):
record = self.root / f"started-{service}-{config}.json"
self.assertEqual(json.loads(record.read_text()), [f"configs/{config}"])
self.assertTrue((self.root / "logs" / f"{service}-{config}.log").is_file())
# A failed restart must also leave the supervisor and other rooms up.
deadline = time.monotonic() + 5
while "failed to restart" not in (self.root / "launcher.log").read_text():
if time.monotonic() >= deadline:
self.fail("Failed room was not retried")
time.sleep(0.05)
self.assertIsNone(process.poll())
self.assertFalse((self.root / "unexpected-go-invocation").exists())
self.assertFalse((self.root / "unexpected-service-started").exists())
self.assertFalse((pid_dir / "retired.cmd").exists())
for config in configs[:2]:
pid = int((self.root / ".pids" / f"scraper-{config}.pid").read_text())
os.kill(pid, 0)
def test_no_active_configs_reports_error(self):
(self.root / "configs/config_example.yaml").write_text("room_id: template\n")
process = self.launch()
process.wait(timeout=5)
self.assertIn("no room configs found", (self.root / "launcher.log").read_text())
self.assertEqual(list(self.root.glob("started-*.json")), [])
if __name__ == "__main__":
unittest.main()