Conformance checker bug: also look for subtypes of associations when checking multiplicities
This commit is contained in:
parent
641e4b9810
commit
b73ca789cc
1 changed files with 44 additions and 45 deletions
|
|
@ -1,5 +1,4 @@
|
||||||
from services.bottom.V0 import Bottom
|
from services.bottom.V0 import Bottom
|
||||||
from services import od
|
|
||||||
from services.primitives.actioncode_type import ActionCode
|
from services.primitives.actioncode_type import ActionCode
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from state.base import State
|
from state.base import State
|
||||||
|
|
@ -320,67 +319,66 @@ class Conformance:
|
||||||
self.deref_primitive_values()
|
self.deref_primitive_values()
|
||||||
self.precompute_multiplicities()
|
self.precompute_multiplicities()
|
||||||
errors = []
|
errors = []
|
||||||
for tm_name in self.type_model_names.values():
|
for type_name in self.type_model_names.values():
|
||||||
# abstract classes
|
# abstract classes
|
||||||
if tm_name in self.abstract_types:
|
if type_name in self.abstract_types:
|
||||||
type_count = list(self.type_mapping.values()).count(tm_name)
|
type_count = list(self.type_mapping.values()).count(type_name)
|
||||||
if type_count > 0:
|
if type_count > 0:
|
||||||
errors.append(f"Invalid instantiation of abstract class: '{tm_name}'")
|
errors.append(f"Invalid instantiation of abstract class: '{type_name}'")
|
||||||
# class multiplicities
|
# class multiplicities
|
||||||
if tm_name in self.multiplicities:
|
if type_name in self.multiplicities:
|
||||||
lc, uc = self.multiplicities[tm_name]
|
lc, uc = self.multiplicities[type_name]
|
||||||
type_count = list(self.type_mapping.values()).count(tm_name)
|
type_count = list(self.type_mapping.values()).count(type_name)
|
||||||
for sub_type in self.sub_types[tm_name]:
|
for sub_type in self.sub_types[type_name]:
|
||||||
type_count += list(self.type_mapping.values()).count(sub_type)
|
type_count += list(self.type_mapping.values()).count(sub_type)
|
||||||
if type_count < lc or type_count > uc:
|
if type_count < lc or type_count > uc:
|
||||||
errors.append(f"Cardinality of type exceeds valid multiplicity range: '{tm_name}' ({type_count})")
|
errors.append(f"Cardinality of type exceeds valid multiplicity range: '{type_name}' ({type_count})")
|
||||||
# association source multiplicities
|
|
||||||
if tm_name in self.source_multiplicities:
|
# association/attribute source multiplicities
|
||||||
tm_element, = self.bottom.read_outgoing_elements(self.type_model, tm_name)
|
if type_name in self.source_multiplicities:
|
||||||
tm_tgt_element = self.bottom.read_edge_target(tm_element)
|
# type is an association
|
||||||
tm_tgt_name = self.type_model_names[tm_tgt_element]
|
type_obj, = self.bottom.read_outgoing_elements(self.type_model, type_name)
|
||||||
lc, uc = self.source_multiplicities[tm_name]
|
tgt_type_obj = self.bottom.read_edge_target(type_obj)
|
||||||
for tgt_obj_name, t in self.type_mapping.items():
|
tgt_type_name = self.type_model_names[tgt_type_obj]
|
||||||
if t == tm_tgt_name or t in self.sub_types[tm_tgt_name]:
|
lc, uc = self.source_multiplicities[type_name]
|
||||||
|
for obj_name, obj_type_name in self.type_mapping.items():
|
||||||
|
if obj_type_name == tgt_type_name or obj_type_name in self.sub_types[tgt_type_name]:
|
||||||
|
# obj's type has this incoming association -> now we will count the number of links typed by it
|
||||||
count = 0
|
count = 0
|
||||||
tgt_obj_node, = self.bottom.read_outgoing_elements(self.model, tgt_obj_name)
|
obj, = self.bottom.read_outgoing_elements(self.model, obj_name)
|
||||||
incoming = self.bottom.read_incoming_edges(tgt_obj_node)
|
incoming = self.bottom.read_incoming_edges(obj)
|
||||||
for i in incoming:
|
for i in incoming:
|
||||||
try:
|
try:
|
||||||
if self.type_mapping[self.model_names[i]] == tm_name:
|
type_of_incoming_link = self.type_mapping[self.model_names[i]]
|
||||||
|
if type_of_incoming_link == type_name or type_of_incoming_link in self.sub_types[type_name]:
|
||||||
count += 1
|
count += 1
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass # for elements not part of model, e.g. morphism links
|
pass # for elements not part of model, e.g. morphism links
|
||||||
if count < lc or count > uc:
|
if count < lc or count > uc:
|
||||||
errors.append(f"Source cardinality of type '{tm_name}' ({count}) out of bounds ({lc}..{uc}) in '{tgt_obj_name}'.")
|
errors.append(f"Source cardinality of type '{type_name}' ({count}) out of bounds ({lc}..{uc}) in '{obj_name}'.")
|
||||||
|
|
||||||
# association target multiplicities
|
# association/attribute target multiplicities
|
||||||
if tm_name in self.target_multiplicities:
|
if type_name in self.target_multiplicities:
|
||||||
tm_element, = self.bottom.read_outgoing_elements(self.type_model, tm_name)
|
# type is an association
|
||||||
# tm_target_element = self.bottom.read_edge_target(tm_element)
|
type_obj, = self.bottom.read_outgoing_elements(self.type_model, type_name)
|
||||||
tm_src_element = self.bottom.read_edge_source(tm_element)
|
src_type_obj = self.bottom.read_edge_source(type_obj)
|
||||||
tm_src_name = self.type_model_names[tm_src_element]
|
src_type_name = self.type_model_names[src_type_obj]
|
||||||
lc, uc = self.target_multiplicities[tm_name]
|
lc, uc = self.target_multiplicities[type_name]
|
||||||
# print("checking assoc", tm_name, "source", tm_src_name)
|
for obj_name, obj_type_name in self.type_mapping.items():
|
||||||
# print("subtypes of", tm_src_name, self.sub_types[tm_src_name])
|
if obj_type_name == src_type_name or obj_type_name in self.sub_types[src_type_name]:
|
||||||
for src_obj_name, t in self.type_mapping.items():
|
# obj's type has this outgoing association -> now we will count the number of links typed by it
|
||||||
if t == tm_src_name or t in self.sub_types[tm_src_name]:
|
|
||||||
# print("got obj", src_obj_name, "of type", t)
|
|
||||||
count = 0
|
count = 0
|
||||||
src_obj_node, = self.bottom.read_outgoing_elements(self.model, src_obj_name)
|
obj, = self.bottom.read_outgoing_elements(self.model, obj_name)
|
||||||
# outgoing = self.bottom.read_incoming_edges(src_obj_node)
|
outgoing = self.bottom.read_outgoing_edges(obj)
|
||||||
outgoing = self.bottom.read_outgoing_edges(src_obj_node)
|
|
||||||
for o in outgoing:
|
for o in outgoing:
|
||||||
try:
|
try:
|
||||||
if self.type_mapping[self.model_names[o]] == tm_name:
|
type_of_outgoing_link = self.type_mapping[self.model_names[o]]
|
||||||
# print("have an outgoing edge", self.model_names[o], self.type_mapping[self.model_names[o]], "---> increase counter")
|
if type_of_outgoing_link == type_name or type_of_outgoing_link in self.sub_types[type_name]:
|
||||||
count += 1
|
count += 1
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass # for elements not part of model, e.g. morphism links
|
pass # for elements not part of model, e.g. morphism links
|
||||||
if count < lc or count > uc:
|
if count < lc or count > uc:
|
||||||
errors.append(f"Target cardinality of type '{tm_name}' ({count}) out of bounds ({lc}..{uc}) in '{src_obj_name}'.")
|
errors.append(f"Target cardinality of type '{type_name}' ({count}) out of bounds ({lc}..{uc}) in '{obj_name}'.")
|
||||||
# else:
|
|
||||||
# print(f"OK: Target cardinality of type {tm_name} ({count}) within bounds ({lc}..{uc}) in {src_obj_name}.")
|
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
def evaluate_constraint(self, code, **kwargs):
|
def evaluate_constraint(self, code, **kwargs):
|
||||||
|
|
@ -390,6 +388,7 @@ class Conformance:
|
||||||
|
|
||||||
funcs = {
|
funcs = {
|
||||||
'read_value': self.state.read_value,
|
'read_value': self.state.read_value,
|
||||||
|
'get': self.odapi.get,
|
||||||
'get_value': self.odapi.get_value,
|
'get_value': self.odapi.get_value,
|
||||||
'get_target': self.odapi.get_target,
|
'get_target': self.odapi.get_target,
|
||||||
'get_source': self.odapi.get_source,
|
'get_source': self.odapi.get_source,
|
||||||
|
|
@ -400,6 +399,7 @@ class Conformance:
|
||||||
'get_type_name': self.odapi.get_type_name,
|
'get_type_name': self.odapi.get_type_name,
|
||||||
'get_outgoing': self.odapi.get_outgoing,
|
'get_outgoing': self.odapi.get_outgoing,
|
||||||
'get_incoming': self.odapi.get_incoming,
|
'get_incoming': self.odapi.get_incoming,
|
||||||
|
'has_slot': self.odapi.has_slot,
|
||||||
}
|
}
|
||||||
# print("evaluating constraint ...", code)
|
# print("evaluating constraint ...", code)
|
||||||
loc = {**kwargs, }
|
loc = {**kwargs, }
|
||||||
|
|
@ -450,11 +450,10 @@ class Conformance:
|
||||||
description = f"Local constraint of \"{type_name}\" in \"{obj_name}\""
|
description = f"Local constraint of \"{type_name}\" in \"{obj_name}\""
|
||||||
# print(description)
|
# print(description)
|
||||||
try:
|
try:
|
||||||
result = self.evaluate_constraint(code, this=obj_id)
|
result = self.evaluate_constraint(code, this=obj_id) # may raise
|
||||||
|
check_result(result, description)
|
||||||
except:
|
except:
|
||||||
errors.append(f"Runtime error during evaluation of {description}:\n{indent(traceback.format_exc(), 6)}")
|
errors.append(f"Runtime error during evaluation of {description}:\n{indent(traceback.format_exc(), 6)}")
|
||||||
# raise Exception(f"Context of above error = {description}") from e
|
|
||||||
check_result(result, description)
|
|
||||||
|
|
||||||
# global constraints
|
# global constraints
|
||||||
glob_constraints = []
|
glob_constraints = []
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue