115 lines
4.9 KiB
Python
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()
|