From e8242aa80c8482ec88f98634f6e650275ea05d85 Mon Sep 17 00:00:00 2001 From: Shonda-Adena-Witherspoon Date: Fri, 2 May 2025 13:54:25 -0500 Subject: [PATCH 01/16] add helper utils, pytest fixtures, and smoke test for vllm-detecor-adapter Signed-off-by: Shonda-Adena-Witherspoon --- tests/conftest.py | 101 ++++++++++++++++++++++++++++++++++++++ tests/test_http_server.py | 6 +++ tests/utils.py | 45 +++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 tests/conftest.py create mode 100644 tests/test_http_server.py create mode 100644 tests/utils.py diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..63b508b --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,101 @@ +""" +Pytest fixtures for spinning up a live vllm-detector-adapter HTTP server +""" +from __future__ import annotations + +import argparse +import asyncio +import signal +import sys +import threading +from collections.abc import Generator + +import pytest +import requests +from vllm.entrypoints.openai.cli_args import make_arg_parser +from vllm.utils import FlexibleArgumentParser + +from vllm_detector_adapter.api_server import run_server, add_chat_detection_params +from vllm_detector_adapter.utils import LocalEnvVarArgumentParser +from .utils import get_random_port, wait_until, TaskFailedError + +@pytest.fixture(scope="session") +def http_server_port() -> int: + """Port for the http server""" + return get_random_port() + + +@pytest.fixture(scope="session") +def http_server_url(http_server_port: int) -> str: + """Url for the http server""" + return f"http://localhost:{http_server_port}" + + +@pytest.fixture +def args(monkeypatch, http_server_port: int) -> argparse.Namespace: + """Mimic: python -m vllm_detector_adapter.api_server --model …""" + # Use a 'tiny' model for test purposes + model_name = "facebook/opt-125m" + + mock_argv = [ + "__main__.py", + "--model", model_name, + f"--port={http_server_port}", + "--host=localhost", + "--dtype=float32", + + ] + monkeypatch.setattr(sys, "argv", mock_argv, raising=False) + + # Build parser like __main__ in api.server.py + base_parser = FlexibleArgumentParser(description="vLLM server setup for pytest.") + parser = LocalEnvVarArgumentParser(parser=make_arg_parser(base_parser)) + parser = add_chat_detection_params(parser) + return parser.parse_args() + + +@pytest.fixture +def _servers( + args: argparse.Namespace, + http_server_url: str, + monkeypatch, +) -> Generator[None, None, None]: + """Start server in background thread""" + loop = asyncio.new_event_loop() + task: asyncio.Task | None = None + + # Patch signal handling so child threads don’t touch the OS handler table + monkeypatch.setattr(loop, "add_signal_handler", lambda *args, **kwargs: None) + monkeypatch.setattr(signal, "signal", lambda *args, **kwargs: None) + + def target() -> None: + nonlocal task + task = loop.create_task(run_server(args)) + try: + loop.run_until_complete(task) + finally: + loop.close() + + t = threading.Thread(target=target, name="vllm-detector-server") + t.start() + + def _health() -> bool: + if task and task.done(): + raise TaskFailedError(task.exception()) + requests.get(f"{http_server_url}/health", timeout=1).raise_for_status() + return True + + try: + wait_until(_health, timeout=60.0, interval=1.0) + # tests execute with live server + yield + finally: + if task: + task.cancel() + t.join() + + +@pytest.fixture +def api_base_url(_servers, http_server_url: str) -> str: + """Pulls up the server and returns the URL to tests""" + return http_server_url diff --git a/tests/test_http_server.py b/tests/test_http_server.py new file mode 100644 index 0000000..9761d4e --- /dev/null +++ b/tests/test_http_server.py @@ -0,0 +1,6 @@ +import requests + +def test_startup(api_base_url): + """Smoke-test: test that the servers starts and health endpoint returns a 200 status code""" + r = requests.get(f"{api_base_url}/health", timeout=5) + assert r.status_code == 200 diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 0000000..f1ded5e --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,45 @@ +"""Utility helpers shared by the test suite.""" +from __future__ import annotations + +import socket +import time +from typing import Callable, TypeVar + +__all__ = ["get_random_port", "wait_until", "TaskFailedError"] + +T = TypeVar("T") +Predicate = Callable[[], bool] + + +class TaskFailedError(RuntimeError): + """Raised when the background server task exits unexpectedly.""" + + +def get_random_port() -> int: + """Get an unused TCP port""" + with socket.socket() as s: + s.bind(("localhost", 0)) + return s.getsockname()[1] + + +def wait_until( + predicate: Predicate, + *, + timeout: float = 30.0, + interval: float = 0.5, +) -> None: + """ + Poll predicate until it returns True or timeout seconds elapse. + """ + deadline = time.monotonic() + timeout + while True: + try: + if predicate(): + return + except Exception: + pass + + if time.monotonic() >= deadline: + raise TimeoutError("Timed out waiting for condition") + + time.sleep(interval) From bea675ed3a3d3af59bc6c628b1c19b7a3325cd0f Mon Sep 17 00:00:00 2001 From: Shonda-Adena-Witherspoon Date: Fri, 2 May 2025 14:06:44 -0500 Subject: [PATCH 02/16] fix formatting errors Signed-off-by: Shonda-Adena-Witherspoon --- tests/conftest.py | 24 +++++++++++++++--------- tests/test_http_server.py | 2 ++ tests/utils.py | 5 ++++- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 63b508b..2b6f253 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,23 +1,29 @@ """ Pytest fixtures for spinning up a live vllm-detector-adapter HTTP server """ + +# Future from __future__ import annotations +# Standard +from collections.abc import Generator import argparse import asyncio import signal import sys import threading -from collections.abc import Generator -import pytest -import requests +# Third Party from vllm.entrypoints.openai.cli_args import make_arg_parser from vllm.utils import FlexibleArgumentParser +import pytest +import requests -from vllm_detector_adapter.api_server import run_server, add_chat_detection_params +# Local +from .utils import TaskFailedError, get_random_port, wait_until +from vllm_detector_adapter.api_server import add_chat_detection_params, run_server from vllm_detector_adapter.utils import LocalEnvVarArgumentParser -from .utils import get_random_port, wait_until, TaskFailedError + @pytest.fixture(scope="session") def http_server_port() -> int: @@ -31,7 +37,7 @@ def http_server_url(http_server_port: int) -> str: return f"http://localhost:{http_server_port}" -@pytest.fixture +@pytest.fixture def args(monkeypatch, http_server_port: int) -> argparse.Namespace: """Mimic: python -m vllm_detector_adapter.api_server --model …""" # Use a 'tiny' model for test purposes @@ -39,11 +45,11 @@ def args(monkeypatch, http_server_port: int) -> argparse.Namespace: mock_argv = [ "__main__.py", - "--model", model_name, + "--model", + model_name, f"--port={http_server_port}", "--host=localhost", "--dtype=float32", - ] monkeypatch.setattr(sys, "argv", mock_argv, raising=False) @@ -88,7 +94,7 @@ def _health() -> bool: try: wait_until(_health, timeout=60.0, interval=1.0) # tests execute with live server - yield + yield finally: if task: task.cancel() diff --git a/tests/test_http_server.py b/tests/test_http_server.py index 9761d4e..1f885a3 100644 --- a/tests/test_http_server.py +++ b/tests/test_http_server.py @@ -1,5 +1,7 @@ +# Third Party import requests + def test_startup(api_base_url): """Smoke-test: test that the servers starts and health endpoint returns a 200 status code""" r = requests.get(f"{api_base_url}/health", timeout=5) diff --git a/tests/utils.py b/tests/utils.py index f1ded5e..b1129c3 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,9 +1,12 @@ """Utility helpers shared by the test suite.""" + +# Future from __future__ import annotations +# Standard +from typing import Callable, TypeVar import socket import time -from typing import Callable, TypeVar __all__ = ["get_random_port", "wait_until", "TaskFailedError"] From a8e2a8702b330b6ab04f84507da7046f0c7a7b00 Mon Sep 17 00:00:00 2001 From: Shonda-Adena-Witherspoon Date: Fri, 2 May 2025 14:23:17 -0500 Subject: [PATCH 03/16] set device type Signed-off-by: Shonda-Adena-Witherspoon --- tests/conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/conftest.py b/tests/conftest.py index 2b6f253..67b6af0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -50,6 +50,7 @@ def args(monkeypatch, http_server_port: int) -> argparse.Namespace: f"--port={http_server_port}", "--host=localhost", "--dtype=float32", + "--device=cpu", ] monkeypatch.setattr(sys, "argv", mock_argv, raising=False) From 85309f046fd80fb03510bc774ee676609ffe2ebf Mon Sep 17 00:00:00 2001 From: Shonda-Adena-Witherspoon Date: Fri, 2 May 2025 14:26:41 -0500 Subject: [PATCH 04/16] fix formatting for conftest Signed-off-by: Shonda-Adena-Witherspoon --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 67b6af0..009c852 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -50,7 +50,7 @@ def args(monkeypatch, http_server_port: int) -> argparse.Namespace: f"--port={http_server_port}", "--host=localhost", "--dtype=float32", - "--device=cpu", + "--device=cpu", ] monkeypatch.setattr(sys, "argv", mock_argv, raising=False) From 229d7c2f917f4d290c70d679516beb9818eb056d Mon Sep 17 00:00:00 2001 From: Shonda-Adena-Witherspoon Date: Fri, 2 May 2025 15:40:09 -0500 Subject: [PATCH 05/16] using original tiny model that passes locally Signed-off-by: Shonda-Adena-Witherspoon --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 009c852..75a9446 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -93,7 +93,7 @@ def _health() -> bool: return True try: - wait_until(_health, timeout=60.0, interval=1.0) + wait_until(_health, timeout=120.0, interval=1.0) # tests execute with live server yield finally: From edf31ba54c32255f244be2b1518a4641f63748f2 Mon Sep 17 00:00:00 2001 From: Shonda-Adena-Witherspoon Date: Fri, 2 May 2025 15:55:58 -0500 Subject: [PATCH 06/16] add additional mock arg Signed-off-by: Shonda-Adena-Witherspoon --- tests/conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/conftest.py b/tests/conftest.py index 75a9446..9037c3e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -51,6 +51,7 @@ def args(monkeypatch, http_server_port: int) -> argparse.Namespace: "--host=localhost", "--dtype=float32", "--device=cpu", + "--disable-async-output-proc", ] monkeypatch.setattr(sys, "argv", mock_argv, raising=False) From ec602eb6181f7602393a2d2d900c0747bdd6c200 Mon Sep 17 00:00:00 2001 From: Shonda-Adena-Witherspoon Date: Fri, 2 May 2025 16:09:29 -0500 Subject: [PATCH 07/16] add additional mock args to prevent failures Signed-off-by: Shonda-Adena-Witherspoon --- tests/conftest.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 9037c3e..765df8a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -52,6 +52,9 @@ def args(monkeypatch, http_server_port: int) -> argparse.Namespace: "--dtype=float32", "--device=cpu", "--disable-async-output-proc", + "--enforce-eager", + "--max-model-len", + "128", ] monkeypatch.setattr(sys, "argv", mock_argv, raising=False) From f7d9bb0b04b4a4c75b7053f8ccf80d8f16e97c9f Mon Sep 17 00:00:00 2001 From: Shonda-Adena-Witherspoon Date: Fri, 2 May 2025 16:25:03 -0500 Subject: [PATCH 08/16] add debug to tox.ini Signed-off-by: Shonda-Adena-Witherspoon --- tox.ini | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tox.ini b/tox.ini index 79447ad..ec60149 100644 --- a/tox.ini +++ b/tox.ini @@ -19,6 +19,10 @@ setenv = commands = pytest --cov=vllm_detector_adapter --cov-report=html:coverage-{env_name} --cov-report=xml:coverage-{env_name}.xml --html=durations/{env_name}.html {posargs:tests} -W error::UserWarning ; -W ignore::DeprecationWarning +setenv = + VLLM_ENGINE_DEBUG = 1 + VLLM_ENGINE_STREAM_STDERR = 1 + ; Unclear: We probably want to test wheel packaging ; But! tox will fail when this is set and _any_ interpreter is missing ; Without this, sdist packaging is tested so that's a start. From b6454341676ea4a4e2b4292a94c54b0ae247631f Mon Sep 17 00:00:00 2001 From: Shonda-Adena-Witherspoon Date: Fri, 2 May 2025 16:31:15 -0500 Subject: [PATCH 09/16] correct tox.ini for debugging Signed-off-by: Shonda-Adena-Witherspoon --- tox.ini | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index ec60149..bbf1746 100644 --- a/tox.ini +++ b/tox.ini @@ -15,13 +15,12 @@ passenv = LOG_CHANNEL_WIDTH setenv = DFTYPE = pandas_all + VLLM_ENGINE_DEBUG = 1 + VLLM_ENGINE_STREAM_STDERR = 1 commands = pytest --cov=vllm_detector_adapter --cov-report=html:coverage-{env_name} --cov-report=xml:coverage-{env_name}.xml --html=durations/{env_name}.html {posargs:tests} -W error::UserWarning ; -W ignore::DeprecationWarning -setenv = - VLLM_ENGINE_DEBUG = 1 - VLLM_ENGINE_STREAM_STDERR = 1 ; Unclear: We probably want to test wheel packaging ; But! tox will fail when this is set and _any_ interpreter is missing From 723a183768d987708fda10c858031d6190d14ab6 Mon Sep 17 00:00:00 2001 From: Shonda-Adena-Witherspoon Date: Fri, 2 May 2025 16:49:00 -0500 Subject: [PATCH 10/16] another debug env Signed-off-by: Shonda-Adena-Witherspoon --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index bbf1746..3ff3f75 100644 --- a/tox.ini +++ b/tox.ini @@ -16,7 +16,7 @@ passenv = setenv = DFTYPE = pandas_all VLLM_ENGINE_DEBUG = 1 - VLLM_ENGINE_STREAM_STDERR = 1 + VLLM_ENGINE_LOG_FILE = /dev/stderr commands = pytest --cov=vllm_detector_adapter --cov-report=html:coverage-{env_name} --cov-report=xml:coverage-{env_name}.xml --html=durations/{env_name}.html {posargs:tests} -W error::UserWarning ; -W ignore::DeprecationWarning From 7fbc68d2f30ed8c14083e13a79710a7a8ebae94d Mon Sep 17 00:00:00 2001 From: Shonda-Adena-Witherspoon Date: Fri, 2 May 2025 17:00:44 -0500 Subject: [PATCH 11/16] add log level debug Signed-off-by: Shonda-Adena-Witherspoon --- tox.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 3ff3f75..ccca3ba 100644 --- a/tox.ini +++ b/tox.ini @@ -15,8 +15,7 @@ passenv = LOG_CHANNEL_WIDTH setenv = DFTYPE = pandas_all - VLLM_ENGINE_DEBUG = 1 - VLLM_ENGINE_LOG_FILE = /dev/stderr + VLLM_LOGGING_LEVEL = DEBUG commands = pytest --cov=vllm_detector_adapter --cov-report=html:coverage-{env_name} --cov-report=xml:coverage-{env_name}.xml --html=durations/{env_name}.html {posargs:tests} -W error::UserWarning ; -W ignore::DeprecationWarning From fd7ca0a5534abfed8b4803c9fa67ba655b28782c Mon Sep 17 00:00:00 2001 From: Shonda-Adena-Witherspoon Date: Mon, 5 May 2025 10:23:17 -0500 Subject: [PATCH 12/16] add print debug statements Signed-off-by: Shonda-Adena-Witherspoon --- tests/conftest.py | 6 ++++++ tox.ini | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 765df8a..15aadda 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -12,6 +12,7 @@ import signal import sys import threading +import traceback # Third Party from vllm.entrypoints.openai.cli_args import make_arg_parser @@ -83,7 +84,12 @@ def target() -> None: nonlocal task task = loop.create_task(run_server(args)) try: + print("[conftest] starting run server...", flush=True) loop.run_until_complete(task) + except Exception as e: + print("[conftest] server failed to start:", e, flush=True) + traceback.print_exc + raise finally: loop.close() diff --git a/tox.ini b/tox.ini index ccca3ba..eafce2c 100644 --- a/tox.ini +++ b/tox.ini @@ -17,7 +17,7 @@ setenv = DFTYPE = pandas_all VLLM_LOGGING_LEVEL = DEBUG -commands = pytest --cov=vllm_detector_adapter --cov-report=html:coverage-{env_name} --cov-report=xml:coverage-{env_name}.xml --html=durations/{env_name}.html {posargs:tests} -W error::UserWarning +commands = pytest -s --cov=vllm_detector_adapter --cov-report=html:coverage-{env_name} --cov-report=xml:coverage-{env_name}.xml --html=durations/{env_name}.html {posargs:tests} -W error::UserWarning ; -W ignore::DeprecationWarning From 6d6e39cb92b40eba048230e3a9e9901eb34518ea Mon Sep 17 00:00:00 2001 From: Shonda-Adena-Witherspoon Date: Mon, 5 May 2025 10:53:30 -0500 Subject: [PATCH 13/16] updated mock args Signed-off-by: Shonda-Adena-Witherspoon --- tests/conftest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 15aadda..7497f38 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -16,6 +16,7 @@ # Third Party from vllm.entrypoints.openai.cli_args import make_arg_parser +from vllm.entrypoints.openai.api_server import run_and from vllm.utils import FlexibleArgumentParser import pytest import requests @@ -52,10 +53,9 @@ def args(monkeypatch, http_server_port: int) -> argparse.Namespace: "--host=localhost", "--dtype=float32", "--device=cpu", + "--disable-frontend-multiprocessing", "--disable-async-output-proc", "--enforce-eager", - "--max-model-len", - "128", ] monkeypatch.setattr(sys, "argv", mock_argv, raising=False) From 2acb79af8a804999efe45344fd53ca91af2dd5cb Mon Sep 17 00:00:00 2001 From: Shonda-Adena-Witherspoon Date: Mon, 5 May 2025 11:00:10 -0500 Subject: [PATCH 14/16] fixed import error Signed-off-by: Shonda-Adena-Witherspoon --- tests/conftest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 7497f38..4a1f8ea 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -16,7 +16,6 @@ # Third Party from vllm.entrypoints.openai.cli_args import make_arg_parser -from vllm.entrypoints.openai.api_server import run_and from vllm.utils import FlexibleArgumentParser import pytest import requests From 6a43528f916c133f2b512740cfbb8db0b4d6bffa Mon Sep 17 00:00:00 2001 From: Shonda-Adena-Witherspoon Date: Mon, 5 May 2025 11:30:54 -0500 Subject: [PATCH 15/16] add parsed arg validation Signed-off-by: Shonda-Adena-Witherspoon --- tests/conftest.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 4a1f8ea..f95e8ec 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -15,7 +15,7 @@ import traceback # Third Party -from vllm.entrypoints.openai.cli_args import make_arg_parser +from vllm.entrypoints.openai.cli_args import make_arg_parser, validate_parsed_serve_args from vllm.utils import FlexibleArgumentParser import pytest import requests @@ -62,7 +62,9 @@ def args(monkeypatch, http_server_port: int) -> argparse.Namespace: base_parser = FlexibleArgumentParser(description="vLLM server setup for pytest.") parser = LocalEnvVarArgumentParser(parser=make_arg_parser(base_parser)) parser = add_chat_detection_params(parser) - return parser.parse_args() + args = parser.parse_args() + validate_parsed_serve_args(args) + return args @pytest.fixture From 6810c61297285863c54c72ccc10419f10e3d96a4 Mon Sep 17 00:00:00 2001 From: Shonda-Adena-Witherspoon Date: Tue, 6 May 2025 13:44:54 -0500 Subject: [PATCH 16/16] test vllm cpu installation Signed-off-by: Shonda-Adena-Witherspoon --- .github/workflows/build.yml | 7 +++++++ tox.ini | 24 +++++++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e85249e..404827e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,6 +11,13 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + - name: Install system dependencies + run: | + sudo apt-get update -y + sudo apt-get install -y gcc-12 g++-12 libnuma-dev + sudo update-alternatives \ + --install /usr/bin/gcc gcc /usr/bin/gcc-12 10 \ + --slave /usr/bin/g++ g++ /usr/bin/g++-12 - name: Set up Python 3.11 uses: actions/setup-python@v4 with: diff --git a/tox.ini b/tox.ini index eafce2c..8b8111f 100644 --- a/tox.ini +++ b/tox.ini @@ -6,7 +6,6 @@ description = run tests with pytest with coverage extras = all dev-test - vllm passenv = LOG_LEVEL LOG_FILTERS @@ -16,6 +15,29 @@ passenv = setenv = DFTYPE = pandas_all VLLM_LOGGING_LEVEL = DEBUG + VLLM_TARGET_DEVICE=cpu + +allowlist_externals = + git + rm + sh + +# ── BEFORE running pytest, build & install vLLM v0.8.4 CPU-only from source ── +commands_pre = + # 1) clone exactly vLLM v0.8.4 + rm -rf {envtmpdir}/vllm_source + git clone --branch v0.8.4 \ + https://github.com/vllm-project/vllm.git {envtmpdir}/vllm_source + + # 2) install its Python build deps + {envpython} -m pip install --upgrade pip + {envpython} -m pip install "cmake>=3.26" wheel packaging ninja "setuptools-scm>=8" numpy + {envpython} -m pip install -v -r {envtmpdir}/vllm_source/requirements/cpu.txt \ + --extra-index-url https://download.pytorch.org/whl/cpu + + # 3) build & install vLLM in CPU mode + sh -c "cd {envtmpdir}/vllm_source && VLLM_TARGET_DEVICE=cpu {envpython} setup.py install" + #{envpython} -m pip install {envtmpdir}/vllm_source commands = pytest -s --cov=vllm_detector_adapter --cov-report=html:coverage-{env_name} --cov-report=xml:coverage-{env_name}.xml --html=durations/{env_name}.html {posargs:tests} -W error::UserWarning ; -W ignore::DeprecationWarning