Files
moonraker/tests/conftest.py

245 lines
9.4 KiB
Python
Raw Permalink Normal View History

2023-06-15 12:58:13 +08:00
from __future__ import annotations
import pytest
import pytest_asyncio
import asyncio
import shutil
import re
import pathlib
import sys
import shlex
import tempfile
import subprocess
from typing import Iterator, Dict, AsyncIterator, Any
2024-09-02 13:31:06 +08:00
from moonraker.server import Server
from moonraker.eventloop import EventLoop
from moonraker import utils
2023-06-15 12:58:13 +08:00
import dbtool
from fixtures import KlippyProcess, HttpClient, WebsocketClient
ASSETS = pathlib.Path(__file__).parent.joinpath("assets")
need_klippy_restart = pytest.StashKey[bool]()
def pytest_addoption(parser: pytest.Parser, pluginmanager):
parser.addoption("--klipper-path", action="store", dest="klipper_path")
parser.addoption("--klipper-exec", action="store", dest="klipper_exec")
def interpolate_config(source_path: pathlib.Path,
dest_path: pathlib.Path,
keys: Dict[str, Any]
) -> None:
def interp(match):
return str(keys[match.group(1)])
sub_data = re.sub(r"\${([^}]+)}", interp, source_path.read_text())
dest_path.write_text(sub_data)
@pytest.fixture(scope="session", autouse=True)
def ssl_certs() -> Iterator[Dict[str, pathlib.Path]]:
with tempfile.TemporaryDirectory(prefix="moonraker-certs-") as tmpdir:
tmp_path = pathlib.Path(tmpdir)
cert_path = tmp_path.joinpath("certificate.pem")
key_path = tmp_path.joinpath("privkey.pem")
cmd = (
f"openssl req -newkey rsa:4096 -nodes -keyout {key_path} "
f"-x509 -days 365 -out {cert_path} -sha256 "
"-subj '/C=US/ST=NRW/L=Earth/O=Moonraker/OU=IT/"
"CN=www.moonraker-test.com/emailAddress=mail@moonraker-test.com'"
)
args = shlex.split(cmd)
subprocess.run(args, check=True)
yield {
"ssl_certificate_path": cert_path,
"ssl_key_path": key_path,
}
@pytest.fixture(scope="class")
def event_loop() -> Iterator[asyncio.AbstractEventLoop]:
loop = asyncio.get_event_loop_policy().new_event_loop()
yield loop
loop.close()
@pytest.fixture(scope="session")
def session_args(ssl_certs: Dict[str, pathlib.Path]
) -> Iterator[Dict[str, pathlib.Path]]:
mconf_asset = ASSETS.joinpath(f"moonraker/base_server.conf")
secrets_asset = ASSETS.joinpath(f"moonraker/secrets.ini")
pcfg_asset = ASSETS.joinpath(f"klipper/base_printer.cfg")
with tempfile.TemporaryDirectory(prefix="moonraker-test") as tmpdir:
tmp_path = pathlib.Path(tmpdir)
secrets_dest = tmp_path.joinpath("secrets.ini")
shutil.copy(secrets_asset, secrets_dest)
cfg_path = tmp_path.joinpath("config")
cfg_path.mkdir()
log_path = tmp_path.joinpath("logs")
log_path.mkdir()
db_path = tmp_path.joinpath("database")
db_path.mkdir()
gcode_path = tmp_path.joinpath("gcode_files")
gcode_path.mkdir()
dest_paths = {
"temp_path": tmp_path,
"asset_path": ASSETS,
"config_path": cfg_path,
"database_path": db_path,
"log_path": log_path,
"gcode_path": gcode_path,
"secrets_path": secrets_dest,
"klippy_uds_path": tmp_path.joinpath("klippy_uds"),
"klippy_pty_path": tmp_path.joinpath("klippy_pty"),
"klipper.dict": ASSETS.joinpath("klipper/klipper.dict"),
"mconf_asset": mconf_asset,
"pcfg_asset": pcfg_asset,
}
dest_paths.update(ssl_certs)
mconf_dest = cfg_path.joinpath("moonraker.conf")
dest_paths["moonraker.conf"] = mconf_dest
interpolate_config(mconf_asset, mconf_dest, dest_paths)
pcfg_dest = cfg_path.joinpath("printer.cfg")
dest_paths["printer.cfg"] = pcfg_dest
interpolate_config(pcfg_asset, pcfg_dest, dest_paths)
yield dest_paths
@pytest.fixture(scope="session")
def klippy_session(session_args: Dict[str, pathlib.Path],
pytestconfig: pytest.Config) -> Iterator[KlippyProcess]:
pytestconfig.stash[need_klippy_restart] = False
kpath = pytestconfig.getoption('klipper_path', "~/klipper")
kexec = pytestconfig.getoption('klipper_exec', None)
if kexec is None:
kexec = sys.executable
exec = pathlib.Path(kexec).expanduser()
klipper_path = pathlib.Path(kpath).expanduser()
base_cmd = f"{exec} {klipper_path}/klippy/klippy.py "
kproc = KlippyProcess(base_cmd, session_args)
kproc.start()
yield kproc
kproc.stop()
@pytest.fixture(scope="class")
def klippy(klippy_session: KlippyProcess,
pytestconfig: pytest.Config):
if pytestconfig.stash[need_klippy_restart]:
pytestconfig.stash[need_klippy_restart] = False
klippy_session.restart()
return klippy_session
@pytest.fixture(scope="class")
def path_args(request: pytest.FixtureRequest,
session_args: Dict[str, pathlib.Path],
pytestconfig: pytest.Config
) -> Iterator[Dict[str, pathlib.Path]]:
path_marker = request.node.get_closest_marker("run_paths")
paths: Dict[str, Any] = {
"moonraker_conf": "base_server.conf",
"secrets": "secrets.ini",
"printer_cfg": "base_printer.cfg",
"klippy_uds": None,
}
if path_marker is not None:
paths.update(path_marker.kwargs)
tmp_path = session_args["temp_path"]
cfg_path = session_args["config_path"]
mconf_dest = session_args["moonraker.conf"]
mconf_asset = ASSETS.joinpath(f"moonraker/{paths['moonraker_conf']}")
pcfg_asset = ASSETS.joinpath(f"klipper/{paths['printer_cfg']}")
last_uds = session_args["klippy_uds_path"]
if paths["klippy_uds"] is not None:
tmp_uds = tmp_path.joinpath(paths["klippy_uds"])
session_args["klippy_uds_path"] = tmp_uds
if (
not mconf_asset.samefile(session_args["mconf_asset"]) or
paths["klippy_uds"] is not None
):
session_args['mconf_asset'] = mconf_asset
interpolate_config(mconf_asset, mconf_dest, session_args)
if not pcfg_asset.samefile(session_args["pcfg_asset"]):
pcfg_dest = session_args["printer.cfg"]
session_args["pcfg_asset"] = pcfg_asset
interpolate_config(pcfg_asset, pcfg_dest, session_args)
pytestconfig.stash[need_klippy_restart] = True
if paths["secrets"] != session_args["secrets_path"].name:
secrets_asset = ASSETS.joinpath(f"moonraker/{paths['secrets']}")
secrets_dest = tmp_path.joinpath(paths['secrets'])
shutil.copy(secrets_asset, secrets_dest)
session_args["secrets_path"] = secrets_dest
if "moonraker_log" in paths:
log_path = session_args["log_path"]
session_args['moonraker.log'] = log_path.joinpath(
paths["moonraker_log"])
bkp_dest: pathlib.Path = cfg_path.joinpath(".moonraker.conf.bkp")
if "moonraker_bkp" in paths:
bkp_source = ASSETS.joinpath("moonraker/base_server.conf")
bkp_dest = cfg_path.joinpath(paths["moonraker_bkp"])
interpolate_config(bkp_source, bkp_dest, session_args)
if "database" in paths:
db_source = ASSETS.joinpath(f"moonraker/{paths['database']}")
db_dest = session_args["database_path"]
db_args = {"input": str(db_source), "destination": db_dest}
dbtool.restore(db_args)
yield session_args
log = session_args.pop("moonraker.log", None)
if log is not None and log.is_file():
log.unlink()
if bkp_dest.is_file():
bkp_dest.unlink()
for item in session_args["database_path"].iterdir():
if item.is_file():
item.unlink()
session_args["klippy_uds_path"] = last_uds
if paths["klippy_uds"] is not None:
# restore the original uds path
interpolate_config(mconf_asset, mconf_dest, session_args)
@pytest.fixture(scope="class")
def base_server(path_args: Dict[str, pathlib.Path],
event_loop: asyncio.AbstractEventLoop
) -> Iterator[Server]:
evtloop = EventLoop()
args = {
'config_file': str(path_args['moonraker.conf']),
'log_file': str(path_args.get("moonraker.log", "")),
'software_version': "moonraker-pytest"
}
ql = logger = None
if args["log_file"]:
ql, logger, warning = utils.setup_logging(args)
if warning:
args["log_warning"] = warning
yield Server(args, logger, evtloop)
if ql is not None:
ql.stop()
@pytest_asyncio.fixture(scope="class")
async def full_server(base_server: Server) -> AsyncIterator[Server]:
base_server.load_components()
ret = base_server.server_init(start_server=False)
await asyncio.wait_for(ret, 4.)
yield base_server
if base_server.event_loop.aioloop.is_running():
await base_server._stop_server(exit_reason="terminate")
@pytest_asyncio.fixture(scope="class")
async def ready_server(full_server: Server, klippy: KlippyProcess):
ret = full_server.start_server(connect_to_klippy=False)
await asyncio.wait_for(ret, 4.)
ret = full_server.klippy_connection.connect()
await asyncio.wait_for(ret, 4.)
yield full_server
@pytest_asyncio.fixture(scope="class")
async def http_client() -> AsyncIterator[HttpClient]:
client = HttpClient()
yield client
client.close()
@pytest_asyncio.fixture(scope="class")
async def websocket_client(request: pytest.FixtureRequest
) -> AsyncIterator[WebsocketClient]:
conn_marker = request.node.get_closest_marker("no_ws_connect")
client = WebsocketClient()
if conn_marker is None:
await client.connect()
yield client
client.close()