Files
RecordFlux/tests/unit/graph_test.py
Andres Toom 60dc679878 Add style check for unsigned integers specified as range integers
Ref. eng/recordflux/RecordFlux#1775
2024-11-06 12:01:30 +02:00

293 lines
12 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from pathlib import Path
import pytest
from pydotplus import Dot, InvocationException # type: ignore[attr-defined]
from rflx.expr import FALSE, TRUE, Equal, Greater, Less, Number, Variable
from rflx.graph import create_message_graph, create_state_machine_graph, write_graph
from rflx.identifier import ID
from rflx.model import (
BOOLEAN,
FINAL,
INITIAL,
Field,
Link,
Message,
State,
StateMachine,
Transition,
UnsignedInteger,
declaration as decl,
statement as stmt,
)
from rflx.rapidflux import Location, RecordFluxError
from tests.data import models
def assert_graph(graph: Dot, expected: str, tmp_path: Path) -> None:
path = tmp_path / Path("test.dot")
write_graph(graph, path, fmt="raw")
assert path.read_text().split() == expected.split()
def test_graph_object() -> None:
f_type = UnsignedInteger("P::T", Number(32))
m = Message(
ID("P::M", Location((1, 1))),
structure=[
Link(INITIAL, Field("X"), location=Location((2, 2))),
Link(Field("X"), FINAL, location=Location((3, 3))),
],
types={Field(ID("X", location=Location((4, 4)))): f_type},
location=Location((1, 1), end=(1, 2)),
)
g = create_message_graph(m)
assert [(e.get_source(), e.get_destination()) for e in g.get_edges()] == [
("Initial", "intermediate_0"),
("intermediate_0", "X"),
("X", "intermediate_1"),
("intermediate_1", "Final"),
]
assert [n.get_name() for n in g.get_nodes()] == [
"graph",
"edge",
"node",
"Initial",
"X",
"intermediate_0",
"intermediate_1",
"Final",
]
def test_empty_message_graph(tmp_path: Path) -> None:
m = Message(ID("P::M", Location((1, 1))), [], {}, location=Location((1, 1), end=(1, 2)))
expected = """
digraph "P::M" {
graph [bgcolor="#00000000", pad="0.1", ranksep="0.1 equally", splines=true,
truecolor=true];
edge [color="#6f6f6f", fontcolor="#6f6f6f", fontname="Fira Code", penwidth="2.5"];
node [color="#6f6f6f", fillcolor="#009641", fontcolor="#ffffff", fontname=Arimo,
shape=box, style="rounded,filled", width="1.5"];
Initial [fillcolor="#ffffff", label="", shape=circle, width="0.5"];
intermediate_0 [color="#6f6f6f", fontcolor="#6f6f6f", fontname="Fira Code", height=0,
label="(, 0, ⋆)", penwidth=0, style="", width=0];
Initial -> intermediate_0 [arrowhead=none];
intermediate_0 -> Final [minlen=1];
Final [fillcolor="#6f6f6f", label="", shape=circle, width="0.5"];
}
"""
assert_graph(create_message_graph(m), expected, tmp_path)
def test_dot_graph(tmp_path: Path) -> None:
f_type = UnsignedInteger("P::T", Number(32))
m = Message(
ID("P::M", Location((1, 1))),
structure=[
Link(INITIAL, Field("X"), location=Location((1, 1))),
Link(Field("X"), FINAL, location=Location((2, 2))),
],
types={Field(ID("X", location=Location((1, 1)))): f_type},
location=Location((1, 1), end=(1, 2)),
)
expected = """
digraph "P::M" {
graph [bgcolor="#00000000", pad="0.1", ranksep="0.1 equally", splines=true,
truecolor=true];
edge [color="#6f6f6f", fontcolor="#6f6f6f", fontname="Fira Code", penwidth="2.5"];
node [color="#6f6f6f", fillcolor="#009641", fontcolor="#ffffff", fontname=Arimo,
shape=box, style="rounded,filled", width="1.5"];
Initial [fillcolor="#ffffff", label="", shape=circle, width="0.5"];
X;
intermediate_0 [color="#6f6f6f", fontcolor="#6f6f6f", fontname="Fira Code", height=0,
label="(, 32, ⋆)", penwidth=0, style="", width=0];
Initial -> intermediate_0 [arrowhead=none];
intermediate_0 -> X [minlen=1];
intermediate_1 [color="#6f6f6f", fontcolor="#6f6f6f", fontname="Fira Code", height=0,
label="(, 0, ⋆)", penwidth=0, style="", width=0];
X -> intermediate_1 [arrowhead=none];
intermediate_1 -> Final [minlen=1];
Final [fillcolor="#6f6f6f", label="", shape=circle, width="0.5"];
}
"""
assert_graph(create_message_graph(m), expected, tmp_path)
def test_dot_graph_with_condition(tmp_path: Path) -> None:
f_type = UnsignedInteger("P::T", Number(32))
m = Message(
ID("P::M", Location((1, 1))),
structure=[
Link(INITIAL, Field("X"), location=Location((1, 1))),
Link(
Field("X"),
FINAL,
Greater(Variable("X"), Number(100), location=Location((2, 1))),
location=Location((2, 2)),
),
],
types={Field(ID("X", location=Location((1, 1)))): f_type},
location=Location((1, 1), end=(1, 2)),
)
expected = """
digraph "P::M" {
graph [bgcolor="#00000000", pad="0.1", ranksep="0.1 equally", splines=true,
truecolor=true];
edge [color="#6f6f6f", fontcolor="#6f6f6f", fontname="Fira Code", penwidth="2.5"];
node [color="#6f6f6f", fillcolor="#009641", fontcolor="#ffffff", fontname=Arimo,
shape=box, style="rounded,filled", width="1.5"];
Initial [fillcolor="#ffffff", label="", shape=circle, width="0.5"];
X;
intermediate_0 [color="#6f6f6f", fontcolor="#6f6f6f", fontname="Fira Code", height=0,
label="(, 32, ⋆)", penwidth=0, style="", width=0];
Initial -> intermediate_0 [arrowhead=none];
intermediate_0 -> X [minlen=1];
intermediate_1 [color="#6f6f6f", fontcolor="#6f6f6f", fontname="Fira Code", height=0,
label="(X > 100, 0, ⋆)", penwidth=0, style="", width=0];
X -> intermediate_1 [arrowhead=none];
intermediate_1 -> Final [minlen=1];
Final [fillcolor="#6f6f6f", label="", shape=circle, width="0.5"];
}
"""
assert_graph(create_message_graph(m), expected, tmp_path)
def test_dot_graph_with_double_edge(tmp_path: Path) -> None:
f_type = UnsignedInteger("P::T", Number(32))
m = Message(
ID("P::M", Location((1, 1))),
structure=[
Link(INITIAL, Field("X"), location=Location((1, 1))),
Link(
Field("X"),
FINAL,
Greater(Variable("X"), Number(100), location=Location((3, 3))),
location=Location((2, 2)),
),
Link(
Field("X"),
FINAL,
Less(Variable("X"), Number(50), location=Location((4, 4))),
location=Location((3, 3)),
),
],
types={Field(ID("X", location=Location((1, 1)))): f_type},
location=Location((1, 1), end=(1, 2)),
)
expected = """
digraph "P::M" {
graph [bgcolor="#00000000", pad="0.1", ranksep="0.1 equally", splines=true,
truecolor=true];
edge [color="#6f6f6f", fontcolor="#6f6f6f", fontname="Fira Code", penwidth="2.5"];
node [color="#6f6f6f", fillcolor="#009641", fontcolor="#ffffff", fontname=Arimo,
shape=box, style="rounded,filled", width="1.5"];
Initial [fillcolor="#ffffff", label="", shape=circle, width="0.5"];
X;
intermediate_0 [color="#6f6f6f", fontcolor="#6f6f6f", fontname="Fira Code", height=0,
label="(, 32, ⋆)", penwidth=0, style="", width=0];
Initial -> intermediate_0 [arrowhead=none];
intermediate_0 -> X [minlen=1];
intermediate_1 [color="#6f6f6f", fontcolor="#6f6f6f", fontname="Fira Code", height=0,
label="(X < 50, 0, ⋆)", penwidth=0, style="", width=0];
X -> intermediate_1 [arrowhead=none];
intermediate_1 -> Final [minlen=1];
intermediate_2 [color="#6f6f6f", fontcolor="#6f6f6f", fontname="Fira Code", height=0,
label="(X > 100, 0, ⋆)", penwidth=0, style="", width=0];
X -> intermediate_2 [arrowhead=none];
intermediate_2 -> Final [minlen=1];
Final [fillcolor="#6f6f6f", label="", shape=circle, width="0.5"];
}
"""
assert_graph(create_message_graph(m), expected, tmp_path)
def test_state_machine_graph(tmp_path: Path) -> None:
s = StateMachine(
identifier="P::S",
states=[
State(
"START",
transitions=[
Transition(target=ID("STATE"), condition=Equal(Variable("Global"), TRUE)),
Transition(target=ID("null")),
],
exception_transition=Transition(target=ID("null")),
),
State(
"STATE",
transitions=[Transition(target=ID("IGNORED_1")), Transition(target=ID("null"))],
actions=[stmt.VariableAssignment("Global", FALSE), stmt.Reset("Local")],
declarations=[decl.VariableDeclaration("Local", "TLV::Message")],
),
State(
"IGNORED_1",
transitions=[Transition(target=ID("null"))],
),
],
declarations=[decl.VariableDeclaration("Global", "Boolean")],
parameters=[],
types=[BOOLEAN, models.tlv_message()],
)
expected_full = r"""
digraph "P::S" {
graph [bgcolor="#00000000", pad="0.1", ranksep="0.1 equally", splines=true,
truecolor=true];
edge [color="#6f6f6f", fontcolor="#6f6f6f", fontname="Fira Code", penwidth="2.5"];
node [color="#6f6f6f", fillcolor="#009641", fontcolor="#ffffff", fontname=Arimo,
shape=box, style="rounded,filled", width="1.5"];
START [fillcolor="#ffffff", fontcolor=black];
START -> STATE [minlen=3, tooltip="START → STATE\n\n[0] Global = True"];
START -> Final [minlen=3, tooltip=""];
STATE;
STATE -> IGNORED_1 [minlen=3, tooltip=""];
STATE -> Final [minlen=3, tooltip=""];
IGNORED_1;
IGNORED_1 -> Final [minlen=3, tooltip=""];
Final [fillcolor="#6f6f6f", label="", shape=circle, width="0.5"];
}
"""
assert_graph(create_state_machine_graph(s), expected_full, tmp_path)
expected_filtered = r"""
digraph "P::S" {
graph [bgcolor="#00000000", pad="0.1", ranksep="0.1 equally", splines=true,
truecolor=true];
edge [color="#6f6f6f", fontcolor="#6f6f6f", fontname="Fira Code", penwidth="2.5"];
node [color="#6f6f6f", fillcolor="#009641", fontcolor="#ffffff", fontname=Arimo,
shape=box, style="rounded,filled", width="1.5"];
START [fillcolor="#ffffff", fontcolor=black];
START -> STATE [minlen=3, tooltip="START → STATE\n\n[0] Global = True"];
START -> Final [minlen=3, tooltip=""];
STATE;
STATE -> Final [minlen=3, tooltip=""];
Final [fillcolor="#6f6f6f", label="", shape=circle, width="0.5"];
}
"""
assert_graph(create_state_machine_graph(s, ignore=[r"^IGNORED_"]), expected_filtered, tmp_path)
def test_missing_graphviz(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
def write_mock(self: object, path: object, format: str = "") -> object: # noqa: ARG001, A002
raise InvocationException("GraphViz not found")
monkeypatch.setattr(Dot, "write", write_mock)
with pytest.raises(
RecordFluxError,
match=(
r"^"
r"error: GraphViz not found\n"
r"note: GraphViz is required for creating graphs"
r"$"
),
):
write_graph(Dot(""), tmp_path / "graph")