diff --git a/include/vara/Feature/Error.h b/include/vara/Feature/Error.h index 162c07de5..4cfd0aacf 100644 --- a/include/vara/Feature/Error.h +++ b/include/vara/Feature/Error.h @@ -22,7 +22,8 @@ enum FTErrorCode { MISSING_FEATURE, MISSING_MODEL, MISSING_PARENT, - NON_LEAF_NODE + NON_LEAF_NODE, + CONSTRAINT_MALFORMED }; } // namespace feature @@ -66,6 +67,8 @@ class Error { case vara::feature::NON_LEAF_NODE: OS << "Not a leaf node."; break; + case vara::feature::CONSTRAINT_MALFORMED: + OS << "Constraint is malformed."; } return OS; } diff --git a/include/vara/Feature/FeatureModel.h b/include/vara/Feature/FeatureModel.h index c50543e7c..a006761e1 100644 --- a/include/vara/Feature/FeatureModel.h +++ b/include/vara/Feature/FeatureModel.h @@ -286,6 +286,15 @@ class FeatureModel { return Constraints.back().get(); } + void removeConstraint(Constraint *C) { + // TODO se-sic/VaRA#701 implement tree based comparison + Constraints.erase( + std::find_if(Constraints.begin(), Constraints.end(), + [C](const std::unique_ptr &UniC) { + return UniC.get() == C; + })); + } + /// Delete a \a Feature. void removeFeature(Feature &Feature); diff --git a/include/vara/Feature/FeatureModelTransaction.h b/include/vara/Feature/FeatureModelTransaction.h index 2cfe9e4cc..78d655ce9 100644 --- a/include/vara/Feature/FeatureModelTransaction.h +++ b/include/vara/Feature/FeatureModelTransaction.h @@ -138,6 +138,10 @@ class FeatureModelTransaction } } + void removeConstraint(Constraint &RemoveConstraint) { + this->removeConstraintImpl(RemoveConstraint); + } + void setName(std::string Name) { return this->setNameImpl(std::move(Name)); } void setCommit(std::string Commit) { @@ -252,6 +256,14 @@ class FeatureModelModification { return FM.addConstraint(std::move(Constraint)); } + static void removeConstraint(FeatureModel &FM, Constraint *R) { + FM.removeConstraint(R); + } + + static void removeConstraint(Feature &F, Constraint *C) { + F.removeConstraintNonPreserve(C); + } + static void setName(FeatureModel &FM, std::string NewName) { FM.setName(std::move(NewName)); } @@ -308,8 +320,12 @@ class AddFeatureToModel : public FeatureModelModification { return ALREADY_PRESENT; } if (Parent) { - setParent(*InsertedFeature, *Parent); - addEdge(*Parent, *InsertedFeature); + FeatureTreeNode *ParentNode = Parent; + if (!Parent->getChildren(1).empty()) { + ParentNode = *(Parent->getChildren(1).begin()); + } + setParent(*InsertedFeature, *ParentNode); + addEdge(*ParentNode, *InsertedFeature); } else if (FM.getRoot()) { setParent(*InsertedFeature, *FM.getRoot()); addEdge(*FM.getRoot(), *InsertedFeature); @@ -326,6 +342,46 @@ class AddFeatureToModel : public FeatureModelModification { Feature *Parent; }; +//===----------------------------------------------------------------------===// +// RemoveConstraintFromModel +//===----------------------------------------------------------------------===// + +class RemoveConstraintFromModel : public FeatureModelModification { + friend class FeatureModelModification; + friend class RemoveFeatureFromModel; + +public: + Result exec(FeatureModel &FM) override { + if (auto E = (*this)(FM); !E) { + return E.getError(); + } + return Ok(); + } + + Result operator()(FeatureModel &FM) { + if (RemoveConstraint.getParent() == nullptr) { + return FTErrorCode::CONSTRAINT_MALFORMED; + } + UncoupleVisitor UV; + RemoveConstraint.accept(UV); + removeConstraint(FM, &RemoveConstraint); + return Ok(); + } + +private: + RemoveConstraintFromModel(Constraint &RemoveConstraint) + : RemoveConstraint(RemoveConstraint) {} + + class UncoupleVisitor : public ConstraintVisitor { + public: + void visit(PrimaryFeatureConstraint *C) override { + removeConstraint(*C->getFeature(), C); + } + }; + + Constraint &RemoveConstraint; +}; + //===----------------------------------------------------------------------===// // RemoveFeatureFromModel //===----------------------------------------------------------------------===// @@ -371,6 +427,16 @@ class RemoveFeatureFromModel : public FeatureModelModification { } } + // TODO (se-passau/VaRA#790): different approches to handle constraints + while (!F->constraints().empty()) { + Constraint *C = *(F->constraints().begin()); + while (C->getParent()) { + C = C->getParent(); + } + RemoveConstraintFromModel RCFM(*C); + RCFM(FM); + } + removeEdge(*F->getParent(), *F); removeFeature(FM, *F); return Ok(); @@ -899,6 +965,13 @@ class FeatureModelCopyTransactionBase { std::move(NewConstraint))(*FM); } + void removeConstraintImpl(Constraint &RemoveConstraint) { + assert(FM && "FeatureModel is null."); + + FeatureModelModification::makeModification( + RemoveConstraint)(*FM); + } + void setNameImpl(std::string Name) { assert(FM && "FeatureModel is null."); @@ -1020,6 +1093,11 @@ class FeatureModelModifyTransactionBase { std::move(NewConstraint))); } + void removeConstraintImpl(Constraint &RemoveConstraint) { + Modifications.push_back(FeatureModelModification::makeUniqueModification< + RemoveConstraintFromModel>(RemoveConstraint)); + } + void setNameImpl(std::string Name) { assert(FM && "FeatureModel is null."); diff --git a/lib/Feature/FeatureModelTransaction.cpp b/lib/Feature/FeatureModelTransaction.cpp index 3e005f55c..d5d0bc20c 100644 --- a/lib/Feature/FeatureModelTransaction.cpp +++ b/lib/Feature/FeatureModelTransaction.cpp @@ -14,6 +14,8 @@ std::unique_ptr featureCopy(Feature &F); bool compareProperties(const Feature &F1, const Feature &F2, bool Strict); +std::optional getFeatureRelationship(Feature &F); + void addFeature(FeatureModel &FM, std::unique_ptr NewFeature, Feature *Parent) { auto Trans = FeatureModelModifyTransaction::openTransaction(FM); @@ -175,7 +177,27 @@ mergeFeatureModels(FeatureModel &FM1, FeatureModel &FM2, bool Strict) { // Is there a similar Feature in the original FM if (Feature *CMP = FM.getFeature(F.getName())) { if (compareProperties(*CMP, F, Strict)) { - // similar feature, maybe merge locations + // similar feature, maybe add relationship + auto FRelationship = getFeatureRelationship(F); + auto CMPRelationship = getFeatureRelationship(*CMP); + if (FRelationship && CMPRelationship) { + // Relationships must match in strict mode + if (Strict && (FRelationship.value()->getKind() != + CMPRelationship.value()->getKind())) { + return false; + } + } else if (FRelationship && !CMPRelationship) { + // Relationship is carried over to work around xml format limits + if (CMP->getChildren().size() < 2) { + Trans.addRelationship(FRelationship.value()->getKind(), + F.getName().str()); + } else if (Strict) { + return false; + } + } + // CMPRelationship is already in the new model + + // merge locations for (FeatureSourceRange const &FSR : F.getLocations()) { if (std::find(CMP->getLocationsBegin(), CMP->getLocationsEnd(), FSR) == CMP->getLocationsEnd()) { @@ -191,9 +213,13 @@ mergeFeatureModels(FeatureModel &FM1, FeatureModel &FM2, bool Strict) { return false; } Trans.addFeature(std::move(Copy), F.getParentFeature()); + // add relationship to new feature + auto Relationship = getFeatureRelationship(F); + if (Relationship.has_value()) { + Trans.addRelationship(Relationship.value()->getKind(), F.getName().str()); + } } - // copy children if missing for (Feature *Child : F.getChildren()) { if (!mergeSubtree(Trans, FM, *Child, Strict)) { return false; @@ -227,9 +253,11 @@ mergeFeatureModels(FeatureModel &FM1, FeatureModel &FM2, bool Strict) { [[nodiscard]] bool compareProperties(const Feature &F1, const Feature &F2, bool Strict) { + // name equality if (F1.getName() != F2.getName()) { return false; } + // parent equality, properties checked before so name equality is sufficient if (F1.getKind() != Feature::FeatureKind::FK_ROOT && F2.getKind() != Feature::FeatureKind::FK_ROOT) { if (*(F1.getParentFeature()) != *(F2.getParentFeature())) { @@ -250,9 +278,6 @@ mergeFeatureModels(FeatureModel &FM1, FeatureModel &FM2, bool Strict) { if (F1.getKind() == Feature::FeatureKind::FK_ROOT) { return true; } - if (F1.getParent()->getKind() != F2.getParent()->getKind()) { - return false; - } if (F1.getKind() == Feature::FeatureKind::FK_BINARY) { return true; } @@ -264,4 +289,14 @@ mergeFeatureModels(FeatureModel &FM1, FeatureModel &FM2, bool Strict) { return false; } +std::optional getFeatureRelationship(Feature &F) { + if (!F.getChildren(1).empty()) { + auto *C = *(F.children().begin()); + if (auto *R = llvm::dyn_cast(C)) { + return R; + } + } + return std::nullopt; +} + } // namespace vara::feature diff --git a/lib/Feature/FeatureModelWriter.cpp b/lib/Feature/FeatureModelWriter.cpp index 20101ad58..c51cc113b 100644 --- a/lib/Feature/FeatureModelWriter.cpp +++ b/lib/Feature/FeatureModelWriter.cpp @@ -27,6 +27,7 @@ int FeatureModelXmlWriter::writeFeatureModel(std::string Path) { std::unique_ptr Writer( xmlNewTextWriterFilename(Path.data(), 0), &xmlFreeTextWriter); if (!Writer) { + llvm::errs() << "Could not create filewriter for " << Path << '\n'; return false; } diff --git a/unittests/Feature/Feature.cpp b/unittests/Feature/Feature.cpp index ac89b3ad4..1d9d57cd5 100644 --- a/unittests/Feature/Feature.cpp +++ b/unittests/Feature/Feature.cpp @@ -95,4 +95,20 @@ TEST(Feature, locationUpdate) { EXPECT_EQ(*TestLCO.getLocationsBegin(), Fsr2); } +TEST(Feature, getChildren) { + FeatureModelBuilder B; + B.makeFeature("a", NumericFeature::ValueListType{1, 2, 3}); + B.addEdge("a", "aa")->makeFeature("aa"); + B.addEdge("a", "ab")->makeFeature("ab"); + + B.emplaceRelationship(Relationship::RelationshipKind::RK_ALTERNATIVE, "a"); + auto FM = B.buildFeatureModel(); + ASSERT_TRUE(FM); + + EXPECT_EQ(FM->getFeature("a")->getChildren(0).size(), 0); + EXPECT_EQ(FM->getFeature("a")->getChildren(1).size(), 0); + EXPECT_EQ(FM->getFeature("a")->getChildren(2).size(), 2); + EXPECT_EQ(FM->getFeature("a")->getChildren(3).size(), 2); +} + } // namespace vara::feature diff --git a/unittests/Feature/FeatureModel.cpp b/unittests/Feature/FeatureModel.cpp index 9554364ca..a52626721 100644 --- a/unittests/Feature/FeatureModel.cpp +++ b/unittests/Feature/FeatureModel.cpp @@ -220,6 +220,34 @@ TEST_F(FeatureModelTest, dfs) { } } +//===----------------------------------------------------------------------===// +// FeatureModelGetChildren Tests +//===----------------------------------------------------------------------===// + +class FeatureModelGetChildrenTest : public ::testing::Test { +protected: + void SetUp() override { + FeatureModelBuilder B; + B.makeFeature("a"); + B.addEdge("a", "aa")->makeFeature("aa"); + B.addEdge("a", "ab")->makeFeature("ab"); + B.emplaceRelationship(Relationship::RelationshipKind::RK_OR, "ab"); + B.addEdge("ab", "aba")->makeFeature("aba"); + B.addEdge("ab", "abb")->makeFeature("abb"); + B.makeFeature("b"); + B.emplaceRelationship(Relationship::RelationshipKind::RK_ALTERNATIVE, "b"); + B.addEdge("b", "ba")->makeFeature("ba"); + B.addEdge("b", "bb")->makeFeature("bb"); + B.makeFeature("c"); + B.addEdge("c", "ca")->makeFeature("ca", std::pair(1, 5)); + B.addEdge("c", "cb")->makeFeature("cb", std::pair(1, 5)); + FM = B.buildFeatureModel(); + ASSERT_TRUE(FM); + } + + std::unique_ptr FM; +}; + //===----------------------------------------------------------------------===// // FeatureModelConsistencyChecker Tests //===----------------------------------------------------------------------===// diff --git a/unittests/Feature/FeatureModelTransaction.cpp b/unittests/Feature/FeatureModelTransaction.cpp index a25220175..6288c8064 100644 --- a/unittests/Feature/FeatureModelTransaction.cpp +++ b/unittests/Feature/FeatureModelTransaction.cpp @@ -1,8 +1,13 @@ #include "vara/Feature/FeatureModelTransaction.h" #include "vara/Feature/FeatureModelBuilder.h" +#include "vara/Feature/FeatureModelParser.h" + +#include "UnittestHelper.h" #include "gtest/gtest.h" +#include "llvm/Support/MemoryBuffer.h" + #include namespace vara::feature { @@ -398,6 +403,228 @@ TEST_F(FeatureModelMergeTransactionTest, AcceptNonStrictDifferenceKind) { EXPECT_EQ(F1->getKind(), Feature::FeatureKind::FK_BINARY); } +//===----------------------------------------------------------------------===// +// FeatureModelMergeRelationshipsTransaction Tests +//===----------------------------------------------------------------------===// + +class FeatureModelMergeRelationshipsTransactionTest : public ::testing::Test { +protected: + void SetUp() override { + FeatureModelBuilder B; + B.makeFeature("a", true); + B.emplaceRelationship(Relationship::RelationshipKind::RK_ALTERNATIVE, "a"); + B.makeFeature("aa", false); + B.makeFeature("ab", false); + B.addEdge("a", "aa"); + B.addEdge("a", "ab"); + FM1 = B.buildFeatureModel(); + ASSERT_TRUE(FM1); + ASSERT_FALSE(FM1->getFeature("a")->getChildren().empty()); + } + + std::unique_ptr FM1; +}; + +TEST_F(FeatureModelMergeRelationshipsTransactionTest, Idempotence) { + size_t FMSizeBefore = FM1->size(); + auto FMMerged = mergeFeatureModels(*FM1, *FM1); + ASSERT_TRUE(FMMerged); + + // Relationship should be in merge result + EXPECT_EQ(FMMerged->size(), FMSizeBefore); + EXPECT_TRUE(FMMerged->getFeature("root")); + ASSERT_TRUE(FMMerged->getFeature("a")); + EXPECT_EQ(FMMerged->getFeature("a")->getChildren().size(), 1); + ASSERT_TRUE(FMMerged->getFeature("aa")); + EXPECT_EQ(FMMerged->getFeature("aa")->getParent()->getKind(), + FeatureTreeNode::NodeKind::NK_RELATIONSHIP); + ASSERT_TRUE(FMMerged->getFeature("ab")); + EXPECT_EQ(FMMerged->getFeature("ab")->getParent()->getKind(), + FeatureTreeNode::NodeKind::NK_RELATIONSHIP); +} + +TEST_F(FeatureModelMergeRelationshipsTransactionTest, + RejectMissingRelationship) { + FeatureModelBuilder B; + B.makeFeature("a", true); + B.makeFeature("aa", false); + B.makeFeature("ab", false); + B.addEdge("a", "aa"); + std::unique_ptr FM2 = B.buildFeatureModel(); + ASSERT_TRUE(FM2); + + // Expect fail, relationships should match if both have more than one child + auto FMMerged = mergeFeatureModels(*FM1, *FM2); + EXPECT_FALSE(FMMerged); + + // Expect fail, also if direction is reversed + FMMerged = mergeFeatureModels(*FM2, *FM1); + EXPECT_FALSE(FMMerged); +} + +TEST_F(FeatureModelMergeRelationshipsTransactionTest, + CheckRelationshipsOfAdded) { + FeatureModelBuilder B; + std::unique_ptr FM2 = B.buildFeatureModel(); + ASSERT_TRUE(FM2); + + // Expect success, feature group of "a" should also be a group after merge. + auto FMMerged = mergeFeatureModels(*FM2, *FM1); + ASSERT_TRUE(FMMerged); + + ASSERT_TRUE(FMMerged->getFeature("a")); + ASSERT_TRUE(FMMerged->getFeature("aa")); + EXPECT_EQ(FMMerged->getFeature("aa")->getParent()->getKind(), + FeatureTreeNode::NodeKind::NK_RELATIONSHIP); + ASSERT_TRUE(FMMerged->getFeature("ab")); + EXPECT_EQ(FMMerged->getFeature("ab")->getParent()->getKind(), + FeatureTreeNode::NodeKind::NK_RELATIONSHIP); +} + +TEST_F(FeatureModelMergeRelationshipsTransactionTest, + AcceptMissingRelationshipOnLeaf) { + FeatureModelBuilder B; + B.makeFeature("a", true); + std::unique_ptr FM2 = B.buildFeatureModel(); + ASSERT_TRUE(FM2); + + // Expect success, feature should be merged even if relationship is only + // present in original. + auto FMMerged = mergeFeatureModels(*FM1, *FM2); + ASSERT_TRUE(FMMerged); + + ASSERT_TRUE(FMMerged->getFeature("a")); + ASSERT_TRUE(FMMerged->getFeature("aa")); + EXPECT_EQ(FMMerged->getFeature("aa")->getParent()->getKind(), + FeatureTreeNode::NodeKind::NK_RELATIONSHIP); + ASSERT_TRUE(FMMerged->getFeature("ab")); + EXPECT_EQ(FMMerged->getFeature("ab")->getParent()->getKind(), + FeatureTreeNode::NodeKind::NK_RELATIONSHIP); +} + +TEST_F(FeatureModelMergeRelationshipsTransactionTest, + AcceptMissingRelationshipOnLeafReversed) { + FeatureModelBuilder B; + B.makeFeature("a", true); + std::unique_ptr FM2 = B.buildFeatureModel(); + ASSERT_TRUE(FM2); + + // Expect success, relationship should be added to childless feature in FM2. + // Fixes XML format shortcomings (groups cannot be specified for 0 or 1 child) + auto FMMerged = mergeFeatureModels(*FM2, *FM1); + ASSERT_TRUE(FMMerged); + + ASSERT_TRUE(FMMerged->getFeature("a")); + ASSERT_TRUE(FMMerged->getFeature("aa")); + EXPECT_EQ(FMMerged->getFeature("aa")->getParent()->getKind(), + FeatureTreeNode::NodeKind::NK_RELATIONSHIP); + ASSERT_TRUE(FMMerged->getFeature("ab")); + EXPECT_EQ(FMMerged->getFeature("ab")->getParent()->getKind(), + FeatureTreeNode::NodeKind::NK_RELATIONSHIP); +} + +TEST_F(FeatureModelMergeRelationshipsTransactionTest, + AcceptMissingRelationshipForSingleChild) { + FeatureModelBuilder B; + B.makeFeature("a", true); + B.makeFeature("ab", false); + B.addEdge("a", "ab"); + std::unique_ptr FM2 = B.buildFeatureModel(); + ASSERT_TRUE(FM2); + + // Expect success, relationship should be added to childless feature in FM2. + // Fixes XML format shortcomings (groups cannot be specified for 0 or 1 child) + auto FMMerged = mergeFeatureModels(*FM1, *FM2); + ASSERT_TRUE(FMMerged); + + ASSERT_TRUE(FMMerged->getFeature("a")); + ASSERT_TRUE(FMMerged->getFeature("aa")); + EXPECT_EQ(FMMerged->getFeature("aa")->getParent()->getKind(), + FeatureTreeNode::NodeKind::NK_RELATIONSHIP); + ASSERT_TRUE(FMMerged->getFeature("ab")); + EXPECT_EQ(FMMerged->getFeature("ab")->getParent()->getKind(), + FeatureTreeNode::NodeKind::NK_RELATIONSHIP); +} + +TEST_F(FeatureModelMergeRelationshipsTransactionTest, + AcceptMissingRelationshipForSingleChildReversed) { + FeatureModelBuilder B; + B.makeFeature("a", true); + B.makeFeature("ab", false); + B.addEdge("a", "ab"); + std::unique_ptr FM2 = B.buildFeatureModel(); + ASSERT_TRUE(FM2); + + // Expect success, relationship should be added to childless feature in FM2. + // Fixes XML format shortcomings (groups cannot be specified for 0 or 1 child) + auto FMMerged = mergeFeatureModels(*FM2, *FM1); + ASSERT_TRUE(FMMerged); + + ASSERT_TRUE(FMMerged->getFeature("a")); + ASSERT_TRUE(FMMerged->getFeature("aa")); + EXPECT_EQ(FMMerged->getFeature("aa")->getParent()->getKind(), + FeatureTreeNode::NodeKind::NK_RELATIONSHIP); + ASSERT_TRUE(FMMerged->getFeature("ab")); + EXPECT_EQ(FMMerged->getFeature("ab")->getParent()->getKind(), + FeatureTreeNode::NodeKind::NK_RELATIONSHIP); +} + +TEST_F(FeatureModelMergeRelationshipsTransactionTest, + MissingRelationshipNonStrict) { + size_t FMSizeBefore = FM1->size(); + FeatureModelBuilder B; + B.makeFeature("a", true); + B.makeFeature("aa", false); + B.makeFeature("ab", false); + B.addEdge("a", "aa"); + B.addEdge("a", "ab"); + std::unique_ptr FM2 = B.buildFeatureModel(); + ASSERT_TRUE(FM2); + ASSERT_EQ(FMSizeBefore, FM2->size()); + + // Expect success, relationships of first Model should be used + auto FMMerged = mergeFeatureModels(*FM1, *FM2, false); + ASSERT_TRUE(FMMerged); + + EXPECT_EQ(FMMerged->size(), FMSizeBefore); + ASSERT_TRUE(FMMerged->getFeature("root")); + ASSERT_TRUE(FMMerged->getFeature("a")); + EXPECT_EQ(FMMerged->getFeature("a")->getChildren().size(), 1); + ASSERT_TRUE(FMMerged->getFeature("aa")); + EXPECT_EQ(FMMerged->getFeature("aa")->getParent()->getKind(), + FeatureTreeNode::NodeKind::NK_RELATIONSHIP); + ASSERT_TRUE(FMMerged->getFeature("ab")); + EXPECT_EQ(FMMerged->getFeature("ab")->getParent()->getKind(), + FeatureTreeNode::NodeKind::NK_RELATIONSHIP); +} + +TEST_F(FeatureModelMergeRelationshipsTransactionTest, + MissingRelationshipNonStrictReversed) { + size_t FMSizeBefore = FM1->size(); + FeatureModelBuilder B; + B.makeFeature("a", true); + B.makeFeature("aa", false); + B.makeFeature("ab", false); + B.addEdge("a", "aa"); + B.addEdge("a", "ab"); + std::unique_ptr FM2 = B.buildFeatureModel(); + ASSERT_TRUE(FM2); + ASSERT_EQ(FMSizeBefore, FM2->size()); + + // Expect success, relationships of first Model should be used + auto FMMerged = mergeFeatureModels(*FM2, *FM1, false); + ASSERT_TRUE(FMMerged); + + EXPECT_EQ(FMMerged->size(), FMSizeBefore); + ASSERT_TRUE(FMMerged->getFeature("root")); + ASSERT_TRUE(FMMerged->getFeature("a")); + // first model, fm2 has no relationship and "a" has 2 children so relationship + // is not moved + EXPECT_EQ(FMMerged->getFeature("a")->getChildren().size(), 0); + ASSERT_TRUE(FMMerged->getFeature("aa")); + ASSERT_TRUE(FMMerged->getFeature("ab")); +} + //===----------------------------------------------------------------------===// // FeatureModelRelationshipTransaction Tests //===----------------------------------------------------------------------===// diff --git a/unittests/Feature/Relationship.cpp b/unittests/Feature/Relationship.cpp index b1e6b4e2c..01e847133 100644 --- a/unittests/Feature/Relationship.cpp +++ b/unittests/Feature/Relationship.cpp @@ -78,4 +78,19 @@ TEST(Relationship, outOfOrder) { } } +TEST(Relationship, getChildren) { + FeatureModelBuilder B; + B.makeFeature("a", NumericFeature::ValueListType{1, 2, 3}); + B.addEdge("a", "aa")->makeFeature("aa"); + B.addEdge("a", "ab")->makeFeature("ab"); + + B.emplaceRelationship(Relationship::RelationshipKind::RK_ALTERNATIVE, "a"); + auto FM = B.buildFeatureModel(); + ASSERT_TRUE(FM); + + EXPECT_EQ(FM->getFeature("a")->getChildren(0).size(), 0); + EXPECT_EQ(FM->getFeature("a")->getChildren(1).size(), 1); + EXPECT_EQ(FM->getFeature("a")->getChildren(2).size(), 1); +} + } // namespace vara::feature