Sardana testing

Overview

pytest is the test runner for the whole Sardana suite. Existing unittest.TestCase tests are still collected by pytest, but new tests should use pytest functions and fixtures.

Tests live next to the code in test subpackages. Resources needed by tests live in test/res subdirectories where appropriate. Bug fixes should normally include a regression test.

The test infrastructure originated in Sardana Enhancement Proposal 5. See the maintained Sardana Enhancement Proposal index for SEP5 and the other design documents.

Install the test environment

Run all commands from the repository root. Pixi installs the project and the supported test dependencies before running a task.

If your environment does not use Pixi, install the project with its supported test dependencies using pip:

python -m pip install -e ".[test]"

Optional GUI, recorder, or backend tests can still require their own optional dependencies.

Run the tests

Test commands

Purpose

Command

Expected scope

Whole suite

pixi run test

All collected tests; optional tests may skip when their dependencies or explicit opt-ins are absent.

No Tango/Redis services or GUI stack

pixi run test-unit

Excludes tango, device_server, gui, and redis; no service needs to be provided or started.

Database integration

pixi run test-tango-db

Selects tango and not device_server and normally starts a fresh isolated pytango-db database per test.

Device-server system tests

pixi run test-system

Selects device_server and normally uses a session-scoped isolated pytango-db database.

Coverage

pixi run test-cov

Writes the HTML report to htmlcov/.

Direct pytest

After installing the test dependencies with pip, run the direct equivalents from the repository root. This ensures that pytest.ini and src/sardana/conftest.py are discovered:

python -m pytest
python -m pytest -m "not tango and not device_server and not gui and not redis"
python -m pytest -m "tango and not device_server"
python -m pytest -m device_server

Using python -m pytest runs pytest with the interpreter used by python -m pip. The equivalent pytest executable also works when it is on PATH.

Target tests

Pass normal pytest arguments through Pixi to run a directory, file, individual test, or marker expression:

pixi run pytest -v src/sardana/pool/test
pixi run pytest src/sardana/test/test_tango_db.py::test_tango_db_is_reachable
pixi run pytest -m "tango and not device_server"

Without Pixi, use the same arguments with the active Python interpreter:

python -m pytest -v src/sardana/pool/test
python -m pytest src/sardana/test/test_tango_db.py::test_tango_db_is_reachable

The project pytest.ini applies short tracebacks, importlib import mode, strict marker checking, skip reporting, and a 600-second per-test timeout.

Test categories and markers

Selection markers group tests by the runtime services and optional stacks they need:

Selection markers

Marker

Meaning

tango

Requires a Tango database.

device_server

Starts a Pool and/or MacroServer device server.

gui

Requires the Qt GUI stack.

redis

Requires TEST_REDIS_URI and a disposable Redis server.

Following the current suite convention, mark a device-server test with both tango and device_server. The tango marker labels a test for selection; the test must still request tango_db, session_tango_db, or a fixture that depends on one of them. The device_server marker automatically activates session_tango_db for compatibility with both pytest and old unittest-style tests.

Register every new marker in pytest.ini or in the plugin that consumes it, because strict marker checking is enabled. The fixture-configuration markers attrs, attribute_values, and kwargs are summarized in the controller testing guide for in-process Pool fixtures. The repository-local Tango fixture keys are described below.

Tests that need a Tango database skip when neither an isolated database nor an opted-in external database can be provided.

Tango database isolation

Important

By default the test fixtures do not use the database selected by an ambient TANGO_HOST. They start isolated pytango-db databases instead. To use an existing database, set both TANGO_HOST and SARDANA_TEST_EXTERNAL_DB. Use only a disposable test database because the suite creates and deletes server, device, alias, and property records.

Use tango_db only for tests that access the database through tango.Database(). It starts a fresh database for each test. Use session_tango_db, normally through a device-server fixture, whenever a test creates a DeviceProxy or starts a server. The Tango client process binds proxy resolution to the first database it uses, so a per-test database is not suitable for proxies.

The external-database opt-in is explicit:

TANGO_HOST=127.0.0.1:10000 SARDANA_TEST_EXTERNAL_DB=1 \
    pixi run test-system

The session database is shared. Custom server, device, controller, alias, and element names must be unique and must be removed during teardown.

Choose a fixture

Test fixtures

Fixture

Scope

Provides / intended use

Pool core fixtures such as pool, mot01, and ct01

function

In-process Pool objects. No Tango database or server. See the controller testing guide and the Pool test API.

tango_db

function

Fresh isolated database and its host:port; database-only tests.

session_tango_db

session

Shared database for device proxies and device servers. Usually reached through another fixture.

tango_pool

function

A running Pool device name.

tango_pool_server

function

An object with device_name and starter attributes, for tests that must restart or inspect the Pool server.

tango_macroserver

function

A running Door name connected to the fixture Pool.

door_macro_executor

function

Registered macro executor connected to the fixture Door.

sar_test_elements

function

Pool plus standard dummy controllers and elements. Translate logical _test_* names with sar_test_elements.name(...).

measurement_group

function

MeasurementGroupHelper bound to sar_test_elements; cleanup is automatic.

The repository-local src/sardana/conftest.py fixtures are provisional. They are discovered for the Sardana source suite and are not the pytest plugin exported to third-party projects. The separately exported plugin is sardana.pool.test.util, registered by the pytest11 entry point in setup.py.

The session-scoped create_tango_pool, create_tango_macroserver, and create_sar_demo chain is specialized support for the existing sar_demo macro tests. It is not the default for a new isolated test.

Configure Tango fixtures

Repository-local Tango fixtures consume these keys from the closest kwargs marker:

  • tango_pool and tango_pool_server accept a properties mapping for Pool device properties.

  • tango_macroserver accepts a properties mapping for MacroServer device properties such as MacroPath.

  • sar_test_elements accepts extra_pseudo_controllers with additional pseudo-controller definitions.

For example, provide a custom macro path to the MacroServer used by door_macro_executor:

1@pytest.mark.kwargs(
2    {
3        "tango_macroserver": {
4            "properties": {"MacroPath": ["/path/to/macros"]}
5        }
6    }
7)
8def test_custom_macro(door_macro_executor):
9    ...

Write a device-server test

Use the fixture-provided Pool name to create a device proxy:

1import pytest
2import tango
3
4pytestmark = [pytest.mark.tango, pytest.mark.device_server]
5
6
7def test_pool_is_running(tango_pool):
8    pool = tango.DeviceProxy(tango_pool)
9    assert pool.state() == tango.DevState.ON

Macro tests can use the fixture-provided executor directly:

1import pytest
2
3from sardana.macroserver.macros.test import MacroTester
4
5pytestmark = [pytest.mark.tango, pytest.mark.device_server]
6
7
8def test_macro_runs(door_macro_executor):
9    MacroTester(door_macro_executor).macro_runs(macro_name="lsdef")

The fixtures own server cleanup and the lifecycle of isolated databases. An explicitly selected external database remains running. A test still owns any custom element or external-backend data that it creates outside the fixtures.

Test elements and parallel sessions

sar_test_elements uses session-unique physical names. Translate historical logical names before using them:

motor_name = sar_test_elements.name("_test_mt_1_1")

Do not rely on a concrete translated value because it changes per process. This translation allows concurrent sessions to share an explicitly selected database without name collisions.

Macro and controller test helpers

For new macro tests, combine door_macro_executor with MacroTester. BaseMacroTestCase, RunMacroTestCase, and RunStopMacroTestCase remain available for existing class-based tests.

Controller plugin authors should use the controller testing guide. Its in-process Pool fixtures are the exported third-party pytest plugin.

Legacy device-server base classes

The following classes remain for backward compatibility only:

Device-server test class replacements

Deprecated class

Replacement

BasePoolTestCase

tango_pool or tango_pool_server

SarTestTestCase

sar_test_elements

BaseMacroServerTestCase

tango_macroserver and, when executing macros, door_macro_executor