muMLE/transformation/schedule/Tests/Test_meta_model.py

502 lines
20 KiB
Python

import io
import os
import sys
import unittest
sys.path.insert(
0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../"))
)
from api.od import ODAPI
from bootstrap.scd import bootstrap_scd
from transformation.schedule.rule_scheduler import RuleScheduler
from state.devstate import DevState
from transformation.ramify import ramify
from util import loader
class Test_Meta_Model(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.dir = os.path.dirname(__file__)
state = DevState()
scd_mmm = bootstrap_scd(state)
with open(f"{cls.dir}/models/mm_petrinet.od") as file:
mm_s = file.read()
with open(f"{cls.dir}/models/m_petrinet.od") as file:
m_s = file.read()
mm = loader.parse_and_check(state, mm_s, scd_mmm, "mm")
m = loader.parse_and_check(state, m_s, mm, "m")
mm_rt_ramified = ramify(state, mm)
cls.model_param = (state, m, mm)
cls.generator_param = (state, mm, mm_rt_ramified)
def setUp(self):
self.model = ODAPI(*self.model_param)
self.out = io.StringIO()
self.generator = RuleScheduler(
*self.generator_param,
directory=self.dir + "/models",
verbose=True,
outstream=self.out,
)
def _test_conformance(
self, file: str, expected_substr_err: dict[tuple[str, str], list[list[str]]]
) -> None:
try:
self.generator.load_schedule(f"schedule/{file}")
errors = self.out.getvalue().split("\u25b8")[1:]
if len(errors) != len(expected_substr_err.keys()):
assert len(errors) == len(expected_substr_err.keys())
for err in errors:
error_lines = err.strip().split("\n")
line = error_lines[0]
for key_pattern in expected_substr_err.keys():
if (key_pattern[0] in line) and (key_pattern[1] in line):
key = key_pattern
break
else:
assert False
expected = expected_substr_err[key]
if (len(error_lines) - 1) != len(expected):
assert (len(error_lines) - 1) == len(expected)
it = error_lines.__iter__()
it.__next__()
for err_line in it:
if not any(
all(exp in err_line for exp in line_exp)
for line_exp in expected
):
assert False
expected_substr_err.pop(key)
except AssertionError:
raise
except Exception as e:
assert False
def test_no_start(self):
self._test_conformance("no_start.od", {("Start", "Cardinality"): []})
def test_no_end(self):
self._test_conformance("no_end.od", {("End", "Cardinality"): []})
def test_multiple_start(self):
self._test_conformance("multiple_start.od", {("Start", "Cardinality"): []})
def test_multiple_end(self):
self._test_conformance("multiple_end.od", {("End", "Cardinality"): []})
def test_connections_start(self):
# try to load the following schedule.
# The schedules contains happy day nodes and faulty nodes.
# Use the error messages to select error location and further validate the multiple reasons of failure.
self._test_conformance(
"connections_start.od",
{
("Start", "start"): [ # locate failure (contains these two substrings), make sure other do not fully overlap -> flakey test
["input exec", "foo_in", "exist"], # 4 total reasons, a reason contains these three substrings
["output exec", "out", "multiple"], # a reason will match to exactly one subnstring list
["output exec", "foo_out", "exist"],
["input data", "in", "exist"],
]
},
)
def test_connections_end(self):
self._test_conformance(
"connections_end.od",
{
("End", "end"): [
["input exec", "foo_in", "exist"],
["output exec", "foo_out", "exist"],
["input data", "in", "multiple"],
["input data", "out2", "exist"],
["output data", "out", "exist"],
]
},
)
def test_connections_match(self):
self._test_conformance(
"connections_match.od",
{
("Match", "m_foo"): [
["input exec", "foo_in", "exist"],
["output exec", "foo", "exist"],
["output exec", "fail", "multiple"],
["input data", "foo_in", "exist"],
["input data", "in", "multiple"],
["output data", "foo_out", "exist"],
]
},
)
def test_connections_rewrite(self):
self._test_conformance(
"connections_rewrite.od",
{
("Rewrite", "r_foo1"): [
["input exec", "foo_in", "exist"],
["output exec", "foo", "exist"],
],
("Rewrite", "r_foo2"): [
["output exec", "out", "multiple"],
["input data", "foo_in", "exist"],
["input data", "in", "multiple"],
["output data", "foo_out", "exist"],
],
},
)
def test_connections_action(self):
self._test_conformance(
"connections_action.od",
{
("Action", "a_foo1"): [
["input exec", "foo_in", "exist"],
["output exec", "out", "multiple"],
["output exec", "foo", "exist"],
["input data", "in1", "multiple"],
],
("Action", "a_foo2"): [
["input exec", "in", "exist"],
["output exec", "out3", "multiple"],
["output exec", "out", "exist"],
["input data", "in", "exist"],
["output data", "out", "exist"],
],
},
)
def test_connections_modify(self):
#TODO:
# see test_connections_merge
self._test_conformance(
"connections_modify.od",
{
("Invalid source", "Conn_exec"): [],
("Invalid target", "Conn_exec"): [],
("Modify", "m_foo"): [
["input data", "foo_in", "exist"],
["output data", "foo_out", "exist"],
["input data", "in", "multiple"],
],
("Modify", "m_exec"): [
["input exec", "in", "exist"],
["input exec", "in", "exist"],
["output exec", "out", "exist"],
]
},
)
def test_connections_merge(self):
#TODO:
# mm:
# association Conn_exec [0..*] Exec -> Exec [0..*] {
# ...;
# }
# m:
# Conn_exec ( Data -> Exec) {...;} -> Invalid source type 'Merge' for link '__Conn_exec_3:Conn_exec' (1)
# -> Invalid target type 'End' for link '__Conn_exec_3:Conn_exec' (2)
# Conn_exec ( Exec -> Data) {...;} -> No error at all, inconsistent and unexpected behaviour (3)
# different combinations behave unexpected
self._test_conformance(
"connections_merge.od",
{
("Invalid source", "Conn_exec"): [], # (1), expected
("Invalid target", "Conn_exec"): [], # (2), invalid error, should not be shown
("Merge", "m_foo"): [
["input data", "foo_in", "exist"],
["input data", "in2", "multiple"],
["output data", "foo_out", "exist"],
],
("Merge", "m_exec"): [ # (3), checked in Merge itself
["input exec", "in", "exist"],
["output exec", "out", "exist"],
],
},
)
def test_connections_store(self):
self._test_conformance(
"connections_store.od",
{
("Store", "s_foo"): [
["input exec", "foo", "exist"],
["output exec", "out", "multiple"],
["output exec", "foo", "exist"],
["input data", "foo_in", "exist"],
["output data", "foo_out", "exist"],
["input data", "2", "multiple"],
],
},
)
def test_connections_schedule(self):
self._test_conformance(
"connections_schedule.od",
{
("Schedule", "s_foo"): [
["output exec", "out", "multiple"],
["input data", "in2", "multiple"],
]
},
)
def test_connections_loop(self):
self._test_conformance(
"connections_loop.od",
{
("Loop", "l_foo"): [
["input exec", "foo_in", "exist"],
["output exec", "out", "multiple"],
["output exec", "foo", "exist"],
["input data", "foo_in", "exist"],
["output data", "foo_out", "exist"],
["input data", "in", "multiple"],
]
},
)
def test_connections_print(self):
self._test_conformance(
"connections_print.od",
{
("Print", "p_foo"): [
["input exec", "foo_in", "exist"],
["output exec", "out", "multiple"],
["output exec", "foo", "exist"],
["input data", "foo_in", "exist"],
["output data", "out", "exist"],
["input data", "in", "multiple"],
]
},
)
def test_fields_start(self):
self._test_conformance(
"fields_start.od",
{
("Start", "Cardinality"): [],
("Start", "string"): [
["Unexpected type", "ports_exec_out", "str"],
["Unexpected type", "ports_data_out", "str"],
],
("Start", '"int"'): [ # included " to avoid flakey test
["Unexpected type", "ports_exec_out", "int"],
["Unexpected type", "ports_data_out", "int"],
],
("Start", "tuple"): [
["Unexpected type", "ports_exec_out", "tuple"],
["Unexpected type", "ports_data_out", "tuple"],
],
("Start", "dict"): [
["Unexpected type", "ports_exec_out", "dict"],
["Unexpected type", "ports_data_out", "dict"],
],
("Start", "none"): [
["Unexpected type", "ports_exec_out", "NoneType"],
["Unexpected type", "ports_data_out", "NoneType"],
],
("Start", "invalid"): [
["Invalid python", "ports_exec_out"],
["Invalid python", "ports_data_out"],
],
("Start", "subtype"): [
["Unexpected type", "ports_exec_out", "list"],
["Unexpected type", "ports_data_out", "list"],
],
("Start", "code"): [
["Unexpected type", "ports_exec_out"],
["Unexpected type", "ports_data_out"],
],
},
)
def test_fields_end(self):
self._test_conformance(
"fields_end.od",
{
("End", "Cardinality"): [],
("End", "string"): [
["Unexpected type", "ports_exec_in", "str"],
["Unexpected type", "ports_data_in", "str"],
],
("End", '"int"'): [
["Unexpected type", "ports_exec_in", "int"],
["Unexpected type", "ports_data_in", "int"],
],
("End", "tuple"): [
["Unexpected type", "ports_exec_in", "tuple"],
["Unexpected type", "ports_data_in", "tuple"],
],
("End", "dict"): [
["Unexpected type", "ports_exec_in", "dict"],
["Unexpected type", "ports_data_in", "dict"],
],
("End", "none"): [
["Unexpected type", "ports_exec_in", "NoneType"],
["Unexpected type", "ports_data_in", "NoneType"],
],
("End", "invalid"): [
["Invalid python", "ports_exec_in"],
["Invalid python", "ports_data_in"],
],
("End", "subtype"): [
["Unexpected type", "ports_exec_in", "list"],
["Unexpected type", "ports_data_in", "list"],
],
("End", "code"): [
["Unexpected type", "ports_exec_in"],
["Unexpected type", "ports_data_in"],
],
},
)
def test_fields_action(self):
self._test_conformance(
"fields_action.od",
{
("cardinality", "Action_action"): [],
("Action", "string"): [
["Unexpected type", "ports_exec_out", "str"],
["Unexpected type", "ports_exec_in", "str"],
["Unexpected type", "ports_data_out", "str"],
["Unexpected type", "ports_data_in", "str"],
],
("Action", '"int"'): [
["Unexpected type", "ports_exec_out", "int"],
["Unexpected type", "ports_exec_in", "int"],
["Unexpected type", "ports_data_out", "int"],
["Unexpected type", "ports_data_in", "int"],
],
("Action", "tuple"): [
["Unexpected type", "ports_exec_out", "tuple"],
["Unexpected type", "ports_exec_in", "tuple"],
["Unexpected type", "ports_data_out", "tuple"],
["Unexpected type", "ports_data_in", "tuple"],
],
("Action", "dict"): [
["Unexpected type", "ports_exec_out", "dict"],
["Unexpected type", "ports_exec_in", "dict"],
["Unexpected type", "ports_data_out", "dict"],
["Unexpected type", "ports_data_in", "dict"],
],
("Action", "none"): [
["Unexpected type", "ports_exec_out", "NoneType"],
["Unexpected type", "ports_exec_in", "NoneType"],
["Unexpected type", "ports_data_out", "NoneType"],
["Unexpected type", "ports_data_in", "NoneType"],
],
('"Action"', '"invalid"'): [
["Invalid python", "ports_exec_out"],
["Invalid python", "ports_exec_in"],
["Invalid python", "ports_data_out"],
["Invalid python", "ports_data_in"],
],
('"Action_action"', '"invalid_action"'): [
["Invalid python code"],
["line"],
],
("Action", "subtype"): [
["Unexpected type", "ports_exec_out", "list"],
["Unexpected type", "ports_exec_in", "list"],
["Unexpected type", "ports_data_out", "list"],
["Unexpected type", "ports_data_in", "list"],
],
("Action", "code"): [
["Unexpected type", "ports_exec_out"],
["Unexpected type", "ports_exec_in"],
["Unexpected type", "ports_data_out"],
["Unexpected type", "ports_data_in"],
],
},
)
def test_fields_modify(self):
self._test_conformance(
"fields_modify.od",
{
("Modify", "string"): [
["Unexpected type", "rename", "str"],
["Unexpected type", "delete", "str"],
],
("Modify", "list"): [["Unexpected type", "rename", "list"]],
("Modify", "set"): [["Unexpected type", "rename", "set"]],
("Modify", "tuple"): [
["Unexpected type", "rename", "tuple"],
["Unexpected type", "delete", "tuple"],
],
("Modify", "dict"): [["Unexpected type", "delete", "dict"]],
("Modify", "none"): [
["Unexpected type", "rename", "NoneType"],
["Unexpected type", "delete", "NoneType"],
],
("Modify", "invalid"): [
["Invalid python", "rename"],
["Invalid python", "delete"],
],
("Modify", "subtype"): [
["Unexpected type", "rename", "dict"],
["Unexpected type", "delete", "list"],
],
("Modify", "code"): [
["Unexpected type", "rename"],
["Unexpected type", "delete"],
],
("Modify", "joined"): [["rename", "delete", "disjoint"]],
},
)
def test_fields_merge(self):
self._test_conformance(
"fields_merge.od",
{
("cardinality", "Merge_ports_data_in"): [],
("Merge", "string"): [["Unexpected type", "ports_data_in", "str"]],
("Merge", "tuple"): [["Unexpected type", "ports_data_in", "tuple"]],
("Merge", "dict"): [["Unexpected type", "ports_data_in", "dict"]],
("Merge", "none"): [["Unexpected type", "ports_data_in", "NoneType"]],
("Merge", "invalid"): [["Invalid python", "ports_data_in"]],
("Merge", "subtype"): [["Unexpected type", "ports_data_in", "list"]],
("Merge", "code"): [["Unexpected type", "ports_data_in"]],
("Merge", "no"): [["Missing", "slot", "ports_data_in"]],
},
)
def test_fields_store(self):
self._test_conformance(
"fields_store.od",
{
("cardinality", "Store_ports"): [],
("Store", "string"): [["Unexpected type", "ports", "str"]],
("Store", "tuple"): [["Unexpected type", "ports", "tuple"]],
("Store", "dict"): [["Unexpected type", "ports", "dict"]],
("Store", "none"): [["Unexpected type", "ports", "NoneType"]],
("Store", "invalid"): [["Invalid python", "ports"]],
("Store", "subtype"): [["Unexpected type", "ports", "list"]],
("Store", "code"): [["Unexpected type", "ports"]],
("Store", "no"): [["Missing", "slot", "ports"]],
},
)
def test_fields_print(self):
self._test_conformance(
"fields_print.od",
{
("Print_custom", "list_custom"): [["Unexpected type", "custom", "list"]],
("Print_custom", "set_custom"): [["Unexpected type", "custom", "set"]],
("Print_custom", "tuple_custom"): [["Unexpected type", "custom", "tuple"]],
("Print_custom", "dict_custom"): [["Unexpected type", "custom", "dict"]],
("Print_custom", "none_custom"): [["Unexpected type", "custom", "NoneType"]],
("Print_custom", "invalid_custom"): [["Invalid python", "custom"]],
("Print_custom", "subtype_custom"): [["Unexpected type", "custom", "list"]],
("Print_custom", "code_custom"): [["Unexpected type", "custom"]],
},
)
if __name__ == "__main__":
unittest.main()