diff --git a/cfmtoolbox/models.py b/cfmtoolbox/models.py index edb095e..6baaa10 100644 --- a/cfmtoolbox/models.py +++ b/cfmtoolbox/models.py @@ -64,16 +64,21 @@ def __str__(self) -> str: @dataclass class CFM: - features: list[Feature] + root: Feature require_constraints: list[Constraint] exclude_constraints: list[Constraint] - def add_feature(self, feature: Feature): - if feature not in self.features: - self.features.append(feature) + @property + def features(self) -> list[Feature]: + features = [self.root] + + for feature in features: + features.extend(feature.children) + + return features def is_unbound(self) -> bool: - return self.features[0].is_unbound() + return self.root.is_unbound() @dataclass @@ -83,11 +88,10 @@ class FeatureNode: def validate(self, cfm: CFM) -> bool: # Check if root feature is valid - root_feature = cfm.features[0] - if root_feature.name != self.value.split("#")[0]: + if cfm.root.name != self.value.split("#")[0]: return False - if not self.validate_children(root_feature): + if not self.validate_children(cfm.root): return False return self.validate_constraints(cfm) diff --git a/cfmtoolbox/plugins/big_m.py b/cfmtoolbox/plugins/big_m.py index 5fa45a9..48bbdb2 100644 --- a/cfmtoolbox/plugins/big_m.py +++ b/cfmtoolbox/plugins/big_m.py @@ -8,25 +8,24 @@ def apply_big_m(model: CFM | None) -> CFM | None: print("No model loaded.") return None - global_upper_bound = get_global_upper_bound(model.features[0]) + global_upper_bound = get_global_upper_bound(model.root) - replace_infinite_upper_bound_with_global_upper_bound( - model.features[0], global_upper_bound - ) + replace_infinite_upper_bound_with_global_upper_bound(model.root, global_upper_bound) print("Successfully applied Big-M global bound.") return model -def get_global_upper_bound(feature: Feature): +def get_global_upper_bound(feature: Feature) -> int: global_upper_bound = feature.instance_cardinality.intervals[-1].upper - local_upper_bound = global_upper_bound # Terminate calculation if the upper bound is infinite - if local_upper_bound is None: + if global_upper_bound is None: return 0 + local_upper_bound = global_upper_bound + # Recursively calculate the global upper bound by multiplying the upper bounds of all paths # from the root feature excluding paths that contain a feature with an infinite upper bound for child in feature.children: diff --git a/cfmtoolbox/plugins/featureide_import.py b/cfmtoolbox/plugins/featureide_import.py index ce9ed0f..47ac9b1 100644 --- a/cfmtoolbox/plugins/featureide_import.py +++ b/cfmtoolbox/plugins/featureide_import.py @@ -85,7 +85,7 @@ def parse_feature(feature_element: Element, parent: Feature | None) -> Feature: return feature -def parse_root(root_element: Element) -> list[Feature]: +def parse_root(root_element: Element) -> tuple[Feature, list[Feature]]: root = parse_feature(root_element, parent=None) features = [root] @@ -93,7 +93,7 @@ def parse_root(root_element: Element) -> list[Feature]: for feature in features: features.extend(feature.children) - return features + return root, features def parse_formula_value_and_feature( @@ -173,17 +173,17 @@ def parse_constraints( return (require_constraints, exclude_constraints, eliminated_constraints) -def parse_cfm(root: Element) -> CFM: - struct = root.find("struct") +def parse_cfm(root_element: Element) -> CFM: + struct = root_element.find("struct") if struct is None: raise TypeError("No valid Feature structure found in XML file") root_struct = struct[0] - features = parse_root(root_struct) + root_feature, all_features = parse_root(root_struct) require_constraints, exclude_constraints, eliminated_constraints = ( - parse_constraints(root.find("constraints"), features) + parse_constraints(root_element.find("constraints"), all_features) ) formatted_eliminated_constraints = [ @@ -198,7 +198,7 @@ def parse_cfm(root: Element) -> CFM: file=sys.stderr, ) - return CFM(features, require_constraints, exclude_constraints) + return CFM(root_feature, require_constraints, exclude_constraints) @app.importer(".xml") diff --git a/cfmtoolbox/plugins/json_export.py b/cfmtoolbox/plugins/json_export.py index 2db149b..63c7f75 100644 --- a/cfmtoolbox/plugins/json_export.py +++ b/cfmtoolbox/plugins/json_export.py @@ -5,9 +5,7 @@ @app.exporter(".json") def export_json(cfm: CFM) -> bytes: - root_feature = cfm.features[0] - - serialized_root = serialize_feature(root_feature) + serialized_root = serialize_feature(cfm.root) serialized_constraints = list( map(serialize_constraint, [*cfm.require_constraints, *cfm.exclude_constraints]) ) diff --git a/cfmtoolbox/plugins/json_import.py b/cfmtoolbox/plugins/json_import.py index 9245278..774a642 100644 --- a/cfmtoolbox/plugins/json_import.py +++ b/cfmtoolbox/plugins/json_import.py @@ -20,7 +20,7 @@ def parse_cfm(serialized_cfm: JSON) -> CFM: f"CFM constraints must be a list: {serialized_cfm['constraints']}" ) - features = parse_root(serialized_cfm["root"]) + root, features = parse_root(serialized_cfm["root"]) constraints = [ parse_constraint(serialized_constraint, features) @@ -31,13 +31,13 @@ def parse_cfm(serialized_cfm: JSON) -> CFM: exclude_constraints = list(filter(lambda c: not c.require, constraints)) return CFM( - features=features, + root=root, require_constraints=require_constraints, exclude_constraints=exclude_constraints, ) -def parse_root(serialized_root: JSON) -> list[Feature]: +def parse_root(serialized_root: JSON) -> tuple[Feature, list[Feature]]: root = parse_feature(serialized_root, parent=None) features = [root] @@ -45,7 +45,7 @@ def parse_root(serialized_root: JSON) -> list[Feature]: for feature in features: features.extend(feature.children) - return features + return root, features def parse_feature(serialized_feature: JSON, /, parent: Feature | None) -> Feature: diff --git a/cfmtoolbox/plugins/one_wise_sampling.py b/cfmtoolbox/plugins/one_wise_sampling.py index a01d53b..08260a9 100644 --- a/cfmtoolbox/plugins/one_wise_sampling.py +++ b/cfmtoolbox/plugins/one_wise_sampling.py @@ -48,7 +48,7 @@ def __init__(self, model: CFM): self.model = model def one_wise_sampling(self) -> list[FeatureNode]: - self.calculate_border_assignments(self.model.features[0]) + self.calculate_border_assignments(self.model.root) samples = [] @@ -75,9 +75,9 @@ def generate_valid_sample(self): while True: self.global_feature_count = defaultdict(int) self.covered_assignments = set() - self.covered_assignments.add((self.model.features[0].name, 1)) + self.covered_assignments.add((self.model.root.name, 1)) random_feature_node = self.generate_random_feature_node_with_assignment( - self.model.features[0] + self.model.root ) if ( random_feature_node.validate(self.model) diff --git a/cfmtoolbox/plugins/random_sampling.py b/cfmtoolbox/plugins/random_sampling.py index 8b7e63a..bf996b6 100644 --- a/cfmtoolbox/plugins/random_sampling.py +++ b/cfmtoolbox/plugins/random_sampling.py @@ -34,9 +34,7 @@ def __init__(self, model: CFM): def random_sampling(self) -> FeatureNode: while True: self.global_feature_count = defaultdict(int) - random_feature_node = self.generate_random_feature_node( - self.model.features[0] - ) + random_feature_node = self.generate_random_feature_node(self.model.root) if random_feature_node.validate(self.model): break diff --git a/cfmtoolbox/plugins/uvl_export.py b/cfmtoolbox/plugins/uvl_export.py index 2078ef6..034b59d 100644 --- a/cfmtoolbox/plugins/uvl_export.py +++ b/cfmtoolbox/plugins/uvl_export.py @@ -139,13 +139,11 @@ def serialize_all_constraints( @app.exporter(".uvl") def export_uvl(cfm: CFM) -> bytes: - root_feature = cfm.features[0] - includes = serialize_includes() - root = serialize_root_feature(root_feature) + root = serialize_root_feature(cfm.root) feature_strings = [ - indent(serialize_features(child), "\t\t\t") for child in root_feature.children + indent(serialize_features(child), "\t\t\t") for child in cfm.root.children ] features = "".join(feature_strings) + "\n" diff --git a/cfmtoolbox/plugins/uvl_import.py b/cfmtoolbox/plugins/uvl_import.py index 026e399..71ec300 100644 --- a/cfmtoolbox/plugins/uvl_import.py +++ b/cfmtoolbox/plugins/uvl_import.py @@ -31,8 +31,13 @@ class ConstraintType(Enum): class CustomListener(UVLPythonListener): - def __init__(self, cfm: CFM): - self.cfm = cfm + def __init__( + self, + exported_features: list[Feature], + exported_require_constraints: list[Constraint], + ): + self.exported_features = exported_features + self.exported_require_constraints = exported_require_constraints self.references: list[str] = [] self.feature_cardinalities: list[Cardinality] = [] self.features: list[Feature] = [] @@ -141,7 +146,7 @@ def exitFeature(self, ctx: UVLPythonParser.FeatureContext): ) self.feature_map[name] = feature self.features.append(feature) - self.cfm.features.append(feature) + self.exported_features.append(feature) if len(self.group_features_count) > 0: self.group_features_count[-1] += 1 else: @@ -204,7 +209,7 @@ def exitFeature(self, ctx: UVLPythonParser.FeatureContext): features, ) parent_feature.children.append(feature) - self.cfm.require_constraints.append( + self.exported_require_constraints.append( Constraint( True, parent_feature, @@ -230,15 +235,17 @@ def exitFeature(self, ctx: UVLPythonParser.FeatureContext): [ Interval( min_cardinality, - max_cardinality - if max_cardinality is not None and max_cardinality > 0 - else None, + ( + max_cardinality + if max_cardinality is not None and max_cardinality > 0 + else None + ), ) ] ) self.feature_map[name] = parent_feature self.features.append(parent_feature) - self.cfm.features.append(parent_feature) + self.exported_features.append(parent_feature) self.groups = self.groups[:-new_groups] if len(self.group_features_count) > 0: self.group_features_count[-1] += 1 @@ -282,7 +289,7 @@ def exitFeature(self, ctx: UVLPythonParser.FeatureContext): child.parent = feature self.feature_map[name] = feature self.features.append(feature) - self.cfm.features.append(feature) + self.exported_features.append(feature) if len(self.group_features_count) > 0: self.group_features_count[-1] += 1 @@ -313,7 +320,7 @@ def exitConstraintLine(self, ctx: UVLPythonParser.ConstraintLineContext): op = self.constraint_types.pop() if op == ConstraintType.IMPLICATION: - self.cfm.require_constraints.append( + self.exported_require_constraints.append( Constraint( True, self.feature_map[ref_1], @@ -323,7 +330,7 @@ def exitConstraintLine(self, ctx: UVLPythonParser.ConstraintLineContext): ) ) elif op == ConstraintType.EQUIVALENCE: - self.cfm.require_constraints.append( + self.exported_require_constraints.append( Constraint( True, self.feature_map[ref_1], @@ -332,7 +339,7 @@ def exitConstraintLine(self, ctx: UVLPythonParser.ConstraintLineContext): Cardinality([Interval(1, None)]), ) ) - self.cfm.require_constraints.append( + self.exported_require_constraints.append( Constraint( True, self.feature_map[ref_2], @@ -380,12 +387,17 @@ def import_uvl(data: bytes): token_stream = CommonTokenStream(lexer) parser = UVLPythonParser(token_stream) - cfm = CFM([], [], []) + exported_features: list[Feature] = [] + exported_require_constraints: list[Constraint] = [] - listener = CustomListener(cfm) + listener = CustomListener(exported_features, exported_require_constraints) parser.removeErrorListeners() parser.addErrorListener(CustomErrorListener()) parser.addParseListener(listener) parser.featureModel() # start parsing - return cfm + return CFM( + root=exported_features[0], + require_constraints=exported_require_constraints, + exclude_constraints=[], + ) diff --git a/tests/plugins/test_big_m.py b/tests/plugins/test_big_m.py index bb3f941..5caa822 100644 --- a/tests/plugins/test_big_m.py +++ b/tests/plugins/test_big_m.py @@ -30,7 +30,7 @@ def test_apply_big_m_with_loaded_model(model: CFM): def test_get_global_upper_bound(model: CFM): - feature = model.features[0] + feature = model.root assert big_m.get_global_upper_bound(feature) == 12 diff --git a/tests/plugins/test_debugging.py b/tests/plugins/test_debugging.py index 00b04b8..964121e 100644 --- a/tests/plugins/test_debugging.py +++ b/tests/plugins/test_debugging.py @@ -108,7 +108,7 @@ def test_stringify_cfm(): ) cfm = CFM( - [feature], + feature, [Constraint(True, feature, Cardinality([]), feature, Cardinality([]))], [], ) diff --git a/tests/plugins/test_featureide_import.py b/tests/plugins/test_featureide_import.py index 13899ef..efdea5d 100644 --- a/tests/plugins/test_featureide_import.py +++ b/tests/plugins/test_featureide_import.py @@ -30,7 +30,7 @@ def test_featureide_import(): path = Path("tests/data/sandwich.xml") cfm = import_featureide(path.read_bytes()) assert len(cfm.features) == 11 - assert cfm.features[0].name == "Sandwich" + assert cfm.root.name == "Sandwich" @pytest.mark.parametrize( @@ -205,8 +205,9 @@ def test_parse_root(): assert struct is not None root_struct = struct[0] - feature_list = parse_root(root_struct) + root_feature, feature_list = parse_root(root_struct) + assert root_feature.name == "Sandwich" assert len(feature_list) == 11 assert feature_list[0].name == "Sandwich" assert feature_list[1].name == "Bread" diff --git a/tests/plugins/test_json_export.py b/tests/plugins/test_json_export.py index 2f2acb9..4fe2853 100644 --- a/tests/plugins/test_json_export.py +++ b/tests/plugins/test_json_export.py @@ -21,7 +21,7 @@ def test_export_json(): ) cfm = CFM( - features=[root], + root=root, require_constraints=[], exclude_constraints=[], ) diff --git a/tests/plugins/test_json_import.py b/tests/plugins/test_json_import.py index 784f5a7..b0ff676 100644 --- a/tests/plugins/test_json_import.py +++ b/tests/plugins/test_json_import.py @@ -16,7 +16,7 @@ def test_import_json(): path = Path("tests/data/sandwich.json") cfm = json_import.import_json(path.read_bytes()) assert len(cfm.features) == 12 - assert cfm.features[0].name == "sandwich" + assert cfm.root.name == "sandwich" @pytest.mark.parametrize( @@ -119,7 +119,7 @@ def test_parse_cfm_parses_root_and_constraints(): def test_parse_root_returns_all_features_in_the_tree(): - features = json_import.parse_root( + root, features = json_import.parse_root( { "name": "sandwich", "instance_cardinality": {"intervals": []}, @@ -159,6 +159,7 @@ def test_parse_root_returns_all_features_in_the_tree(): } ) + assert root.name == "sandwich" assert len(features) == 5 assert features[0].name == "sandwich" assert features[1].name == "meat" diff --git a/tests/plugins/test_uvl_import.py b/tests/plugins/test_uvl_import.py index a577e0d..fda47fa 100644 --- a/tests/plugins/test_uvl_import.py +++ b/tests/plugins/test_uvl_import.py @@ -4,15 +4,13 @@ import pytest import cfmtoolbox.plugins.uvl_import as uvl_import_plugin -from cfmtoolbox import CFM -from cfmtoolbox.models import Cardinality, Constraint, Feature, Interval +from cfmtoolbox import Cardinality, CFMToolbox, Constraint, Feature, Interval from cfmtoolbox.plugins.uvl_import import ConstraintType, CustomListener -from cfmtoolbox.toolbox import CFMToolbox @pytest.fixture() def listener(): - return CustomListener(CFM([], [], [])) + return CustomListener([], []) def test_plugin_can_be_loaded(): @@ -379,7 +377,7 @@ def test_exit_feature_with_two_groups(listener): assert len(listener.features) == 1 assert listener.features[0].name == "test" - mock_parent = copy.deepcopy(listener.cfm.require_constraints[0].first_feature) + mock_parent = copy.deepcopy(listener.exported_require_constraints[0].first_feature) feature1 = Feature( name="test_0", @@ -400,7 +398,7 @@ def test_exit_feature_with_two_groups(listener): ) mock_parent.children = [feature1, feature2] - for child in listener.cfm.require_constraints[0].first_feature.children: + for child in listener.exported_require_constraints[0].first_feature.children: child.parent = mock_parent assert listener.features[0].parent is None @@ -411,15 +409,15 @@ def test_exit_feature_with_two_groups(listener): ) assert listener.feature_map["test"] == listener.features[0] assert len(listener.groups) == 0 - assert len(listener.cfm.require_constraints) == 2 - assert listener.cfm.require_constraints[0] == Constraint( + assert len(listener.exported_require_constraints) == 2 + assert listener.exported_require_constraints[0] == Constraint( require=True, first_feature=mock_parent, first_cardinality=Cardinality([Interval(1, None)]), second_feature=feature1, second_cardinality=Cardinality([Interval(1, None)]), ) - assert listener.cfm.require_constraints[1] == Constraint( + assert listener.exported_require_constraints[1] == Constraint( require=True, first_feature=mock_parent, first_cardinality=Cardinality([Interval(1, None)]), @@ -487,7 +485,7 @@ def test_exit_feature_with_two_groups_and_instance_cardinality(listener): assert len(listener.features) == 1 assert listener.features[0].name == "test" - mock_parent = copy.deepcopy(listener.cfm.require_constraints[0].first_feature) + mock_parent = copy.deepcopy(listener.exported_require_constraints[0].first_feature) feature1 = Feature( name="test_0", @@ -508,7 +506,7 @@ def test_exit_feature_with_two_groups_and_instance_cardinality(listener): ) mock_parent.children = [feature1, feature2] - for child in listener.cfm.require_constraints[0].first_feature.children: + for child in listener.exported_require_constraints[0].first_feature.children: child.parent = mock_parent assert listener.features[0].parent is None @@ -519,15 +517,15 @@ def test_exit_feature_with_two_groups_and_instance_cardinality(listener): ) assert listener.feature_map["test"] == listener.features[0] assert listener.group_features_count == [1] - assert len(listener.cfm.require_constraints) == 2 - assert listener.cfm.require_constraints[0] == Constraint( + assert len(listener.exported_require_constraints) == 2 + assert listener.exported_require_constraints[0] == Constraint( require=True, first_feature=mock_parent, first_cardinality=Cardinality([Interval(1, None)]), second_feature=feature1, second_cardinality=Cardinality([Interval(1, None)]), ) - assert listener.cfm.require_constraints[1] == Constraint( + assert listener.exported_require_constraints[1] == Constraint( require=True, first_feature=mock_parent, first_cardinality=Cardinality([Interval(1, None)]), @@ -594,7 +592,7 @@ def test_exit_feature_with_two_groups_including_group_cardinality(listener): assert len(listener.features) == 1 assert listener.features[0].name == "test" - mock_parent = copy.deepcopy(listener.cfm.require_constraints[0].first_feature) + mock_parent = copy.deepcopy(listener.exported_require_constraints[0].first_feature) feature1 = Feature( name="test_0", @@ -615,7 +613,7 @@ def test_exit_feature_with_two_groups_including_group_cardinality(listener): ) mock_parent.children = [feature1, feature2] - for child in listener.cfm.require_constraints[0].first_feature.children: + for child in listener.exported_require_constraints[0].first_feature.children: child.parent = mock_parent assert listener.features[0].parent is None @@ -626,15 +624,15 @@ def test_exit_feature_with_two_groups_including_group_cardinality(listener): ) assert listener.feature_map["test"] == listener.features[0] assert listener.group_features_count == [1] - assert len(listener.cfm.require_constraints) == 2 - assert listener.cfm.require_constraints[0] == Constraint( + assert len(listener.exported_require_constraints) == 2 + assert listener.exported_require_constraints[0] == Constraint( require=True, first_feature=mock_parent, first_cardinality=Cardinality([Interval(1, None)]), second_feature=feature1, second_cardinality=Cardinality([Interval(1, None)]), ) - assert listener.cfm.require_constraints[1] == Constraint( + assert listener.exported_require_constraints[1] == Constraint( require=True, first_feature=mock_parent, first_cardinality=Cardinality([Interval(1, None)]), @@ -703,7 +701,7 @@ def test_exit_feature_with_two_groups_including_group_cardinality_with_instances assert len(listener.features) == 1 assert listener.features[0].name == "test" - mock_parent = copy.deepcopy(listener.cfm.require_constraints[0].first_feature) + mock_parent = copy.deepcopy(listener.exported_require_constraints[0].first_feature) feature1 = Feature( name="test_0", @@ -724,7 +722,7 @@ def test_exit_feature_with_two_groups_including_group_cardinality_with_instances ) mock_parent.children = [feature1, feature2] - for child in listener.cfm.require_constraints[0].first_feature.children: + for child in listener.exported_require_constraints[0].first_feature.children: child.parent = mock_parent assert listener.features[0].parent is None @@ -735,15 +733,15 @@ def test_exit_feature_with_two_groups_including_group_cardinality_with_instances ) assert listener.feature_map["test"] == listener.features[0] assert listener.group_features_count == [1] - assert len(listener.cfm.require_constraints) == 2 - assert listener.cfm.require_constraints[0] == Constraint( + assert len(listener.exported_require_constraints) == 2 + assert listener.exported_require_constraints[0] == Constraint( require=True, first_feature=mock_parent, first_cardinality=Cardinality([Interval(1, None)]), second_feature=feature1, second_cardinality=Cardinality([Interval(1, None)]), ) - assert listener.cfm.require_constraints[1] == Constraint( + assert listener.exported_require_constraints[1] == Constraint( require=True, first_feature=mock_parent, first_cardinality=Cardinality([Interval(1, None)]), @@ -795,7 +793,7 @@ def test_exit_features_with_one_group_with_group_cardinality(listener): ) assert listener.feature_map["test"] == listener.features[0] assert listener.group_features_count == [1] - assert len(listener.cfm.require_constraints) == 0 + assert len(listener.exported_require_constraints) == 0 def test_exit_group_spec_one_subfeature(listener): @@ -1238,7 +1236,7 @@ def test_exit_constraint_line_equivalence(listener): listener.exitConstraintLine(mock_ctx) - assert len(listener.cfm.require_constraints) == 2 + assert len(listener.exported_require_constraints) == 2 def test_exit_constraint_line_implication(listener): @@ -1266,7 +1264,7 @@ def test_exit_constraint_line_implication(listener): listener.exitConstraintLine(mock_ctx) - assert len(listener.cfm.require_constraints) == 1 + assert len(listener.exported_require_constraints) == 1 def test_exit_features(listener): diff --git a/tests/test_models.py b/tests/test_models.py index 0098675..39ddf62 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -168,41 +168,19 @@ def test_feature_is_unbound(feature: Feature, expectation: bool): assert feature.is_unbound() is expectation -def test_add_feature(): - cfm = CFM([], [], []) - feature = Feature( - "Cheese", Cardinality([]), Cardinality([]), Cardinality([]), None, [] - ) - cfm.add_feature(feature) - assert feature in cfm.features - - -def test_add_feature_ignores_already_added_features(): - cfm = CFM([], [], []) - feature = Feature( - "Cheese", Cardinality([]), Cardinality([]), Cardinality([]), None, [] - ) - cfm.add_feature(feature) - cfm.add_feature(feature) - assert len(cfm.features) == 1 - assert feature in cfm.features - - @pytest.mark.parametrize( ["cfm", "expectation"], [ ( CFM( - [ - Feature( - "Cheese", - Cardinality([Interval(0, None)]), - Cardinality([]), - Cardinality([]), - None, - [], - ) - ], + Feature( + "Cheese", + Cardinality([Interval(0, None)]), + Cardinality([]), + Cardinality([]), + None, + [], + ), [], [], ), @@ -210,16 +188,14 @@ def test_add_feature_ignores_already_added_features(): ), ( CFM( - [ - Feature( - "Cheese", - Cardinality([Interval(0, 4)]), - Cardinality([]), - Cardinality([]), - None, - [], - ) - ], + Feature( + "Cheese", + Cardinality([Interval(0, 4)]), + Cardinality([]), + Cardinality([]), + None, + [], + ), [], [], ), @@ -470,7 +446,7 @@ def test_validate_feature_instance(feature_instance: FeatureNode, expectation: b ), ], ) - cfm = CFM([feature], [], []) + cfm = CFM(feature, [], []) assert feature_instance.validate(cfm) == expectation @@ -759,5 +735,8 @@ def test_validate_constraints( ], ) - cfm = CFM([], require_constraints, exclude_constraints) + dummy_root = Feature( + "Dummy", Cardinality([]), Cardinality([]), Cardinality([]), None, [] + ) + cfm = CFM(dummy_root, require_constraints, exclude_constraints) assert feature_instance.validate_constraints(cfm) == expectation diff --git a/tests/test_toolbox.py b/tests/test_toolbox.py index f7f27d0..78fc039 100644 --- a/tests/test_toolbox.py +++ b/tests/test_toolbox.py @@ -3,8 +3,19 @@ import pytest import typer -from cfmtoolbox import CFM -from cfmtoolbox.toolbox import CFMToolbox +from cfmtoolbox import CFM, Cardinality, CFMToolbox, Feature + + +@pytest.fixture +def root_feature(): + return Feature( + name="root", + instance_cardinality=Cardinality([]), + group_type_cardinality=Cardinality([]), + group_instance_cardinality=Cardinality([]), + parent=None, + children=[], + ) def test_import_model_without_import_path(): @@ -26,12 +37,12 @@ def test_import_model_without_matching_importer(tmp_path: Path): assert app.model is None -def test_import_model_with_matching_importer(tmp_path: Path): +def test_import_model_with_matching_importer(tmp_path: Path, root_feature): app = CFMToolbox() app.import_path = tmp_path / "test.uvl" app.import_path.touch() - cfm = CFM([], [], []) + cfm = CFM(root_feature, [], []) assert app.model is not cfm @app.importer(".uvl") @@ -49,18 +60,18 @@ def test_export_model_without_export_path(): app.export_model() -def test_export_model_without_matching_exporter(tmp_path: Path): +def test_export_model_without_matching_exporter(tmp_path: Path, root_feature): app = CFMToolbox() - app.model = CFM([], [], []) + app.model = CFM(root_feature, [], []) app.export_path = tmp_path / "test.txt" with pytest.raises(typer.Abort, match="Unsupported export format"): app.export_model() -def test_export_model_with_matching_exporter(tmp_path: Path): +def test_export_model_with_matching_exporter(tmp_path: Path, root_feature): app = CFMToolbox() - app.model = CFM([], [], []) + app.model = CFM(root_feature, [], []) app.export_path = tmp_path / "test.uvl" @app.exporter(".uvl") @@ -71,12 +82,12 @@ def export_uvl(cfm: CFM): assert app.export_path.read_text() == "hello" -def test_importer_registration(): +def test_importer_registration(root_feature): app = CFMToolbox() @app.importer(".uvl") def import_uvl(data: bytes): - return CFM([], [], []) + return CFM(root_feature, [], []) assert len(app.registered_importers) == 1 assert app.registered_importers[".uvl"] == import_uvl