245 lines
9.4 KiB
Python
245 lines
9.4 KiB
Python
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
|
|
from moonraker.server import Server
|
|
from moonraker.eventloop import EventLoop
|
|
from moonraker import utils
|
|
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()
|