testing

Helpers for writing pytest tests over SysMLv2/KerML models.

This module provides small, dependency-light utilities for asserting properties about models loaded with syside:

  • describe() renders a human-readable identifier for a model element, used in assertion failure messages.

  • assert_all(), assert_any() and assert_none() assert quantified predicates over an iterable of elements and raise AssertionError with a clear, multi-line message that names the offending elements.

  • model_fixture() builds a pytest fixture that loads a model once per scope (session by default), so a test suite does not reload it per test.

The assertion helpers consume their input iterable exactly once and call the describe callback only for the (capped) set of elements that actually appear in a failure message, so they stay cheap on large models and on the success path.

Index

Functions

describe

Return a default human-readable identifier for a model element.

assert_all

Assert that predicate is truthy for every item in items.

assert_any

Assert that predicate is truthy for at least one item in items.

assert_none

Assert that predicate is truthy for no item in items.

assert_implies

Assert that when(item) truthy implies then(item) truthy.

assert_foreach_unique

Assert that target yields a distinct value for each item.

assert_acyclic

Assert that the directed graph over nodes has no directed cycle.

assert_reachable

Assert that every source can reach at least one target.

model_fixture

Build a pytest fixture that loads a model once per scope.


Functions

describe(item: object) str[source]

Return a default human-readable identifier for a model element.

If item has a non-None qualified_name, return f"{type(item).__name__} {item.qualified_name}" (the qualified name renders to e.g. Pkg::Sub::Name). Otherwise, or if attribute access raises, fall back to repr(item).

This function never raises.

assert_all(items: Iterable[syside.testing.assert_all.T], predicate: Callable[[syside.testing.assert_all.T], object], *, describe: Callable[[syside.testing.assert_all.T], str] = describe, label: str | None = None, limit: int = 10) None[source]

Assert that predicate is truthy for every item in items.

Passes vacuously when items is empty. On failure, raises AssertionError naming the items for which predicate was falsy (the counterexamples), listing at most limit of them.

predicate is called exactly once per item. describe is called only on the items that appear in the failure message.

assert_any(items: Iterable[syside.testing.assert_any.T], predicate: Callable[[syside.testing.assert_any.T], object], *, describe: Callable[[syside.testing.assert_any.T], str] = describe, label: str | None = None, limit: int = 10) None[source]

Assert that predicate is truthy for at least one item in items.

Fails when items is empty. On failure, raises AssertionError stating that none of the examined items satisfied the predicate, listing at most limit of the examined items.

predicate is called exactly once per item, and short-circuits as soon as a truthy result is found. describe is called only on the items that appear in the failure message.

assert_none(items: Iterable[syside.testing.assert_none.T], predicate: Callable[[syside.testing.assert_none.T], object], *, describe: Callable[[syside.testing.assert_none.T], str] = describe, label: str | None = None, limit: int = 10) None[source]

Assert that predicate is truthy for no item in items.

Passes vacuously when items is empty. On failure, raises AssertionError naming the items for which predicate was truthy, listing at most limit of them.

predicate is called exactly once per item. describe is called only on the items that appear in the failure message.

assert_implies(items: Iterable[syside.testing.assert_implies.T], when: Callable[[syside.testing.assert_implies.T], object], then: Callable[[syside.testing.assert_implies.T], object], *, describe: Callable[[syside.testing.assert_implies.T], str] = describe, label: str | None = None, limit: int = 10) None[source]

Assert that when(item) truthy implies then(item) truthy.

For every item, if when is truthy then then must be truthy too. Passes vacuously when items is empty (or no item satisfies when). On failure, raises AssertionError naming the items for which the antecedent held but the consequent failed (the counterexamples), listing at most limit of them.

when is called exactly once per item; then is called only for items where when is truthy (short-circuited otherwise). describe is called only on the items that appear in the failure message.

assert_foreach_unique(items: Iterable[syside.testing.assert_foreach_unique.T], target: Callable[[syside.testing.assert_foreach_unique.T], Hashable], *, describe: Callable[[syside.testing.assert_foreach_unique.T], str] = describe, label: str | None = None, limit: int = 10) None[source]

Assert that target yields a distinct value for each item.

That is, target is injective over items. Passes when items is empty or all target values are distinct. On failure, raises AssertionError naming the target values shared by more than one item, listing at most limit colliding values; for each, the items that share it are listed (also capped at limit, with a suffix).

target is called exactly once per item. describe is called only on the items that appear in the reported collisions.

assert_acyclic(nodes: Iterable[syside.testing.assert_acyclic.T], successors: Callable[[syside.testing.assert_acyclic.T], Iterable[syside.testing.assert_acyclic.T]], *, describe: Callable[[syside.testing.assert_acyclic.T], str] = describe, label: str | None = None) None[source]

Assert that the directed graph over nodes has no directed cycle.

The graph has nodes as its (initial) vertices; any node returned by successors is also part of the graph even if it is not in nodes. A node listed among its own successors is a length-1 cycle.

Traversal is an iterative depth-first search with an explicit stack (never recursion), so graphs thousands of nodes deep do not hit the Python recursion limit. Passes when nodes is empty or the graph is acyclic. On failure, raises AssertionError naming one concrete cycle as a path.

describe is called only on the nodes of the reported cycle.

assert_reachable(sources: Iterable[syside.testing.assert_reachable.T], targets: Iterable[syside.testing.assert_reachable.T], successors: Callable[[syside.testing.assert_reachable.T], Iterable[syside.testing.assert_reachable.T]], *, describe: Callable[[syside.testing.assert_reachable.T], str] = describe, label: str | None = None, limit: int = 10) None[source]

Assert that every source can reach at least one target.

Reachability follows successors forward. A source that is itself a target reaches trivially (distance 0). Passes when sources is empty. When sources is non-empty but targets is empty, every source fails.

On failure, raises AssertionError naming the sources that cannot reach any target (the counterexamples), listing at most limit of them.

describe is called only on the unreachable sources that appear in the failure message.

model_fixture(*paths: str | os.PathLike[str], sysml_source: str | None = None, kerml_source: str | None = None, scope: syside.testing.Scope = 'session', warnings_as_errors: bool = False) Callable[[], syside.Model][source]

Build a pytest fixture that loads a model once per scope.

Provide the model through exactly one of syside.load_model()’s entrypoints – there is no guessing between a path and inline text:

  • positional paths – one or more file or directory paths, or

  • sysml_source – a single inline SysML v2 source string, or

  • kerml_source – a single inline KerML source string.

Passing none, or more than one, of these raises ValueError immediately (when the fixture is defined, not when a test runs).

The fixture loads the model once per scope ("session" by default, so the suite does not reload it per test) and returns the loaded syside.Model. If loading fails to compile – either because syside.load_model() raises syside.ModelError, or because it returns diagnostics for which diagnostics.contains_errors() is true – the fixture fails with an AssertionError describing the diagnostics. With warnings_as_errors=True, diagnostics containing any warnings fail the fixture the same way.

Intended use in a conftest.py:

import syside.testing

model = syside.testing.model_fixture("path/to/models")

or with inline source for a focused test:

model = syside.testing.model_fixture(sysml_source="package P { part def A; }")

The returned object is a pytest fixture; reference it by the name you bind it to (model above) as a test argument.