You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by pa...@apache.org on 2020/01/15 12:41:12 UTC

[sling-org-apache-sling-feature] 01/01: SLING-8998: Add feature-origins metadata entry to artifacts in features when merging

This is an automated email from the ASF dual-hosted git repository.

pauls pushed a commit to branch issues/SLING-8998
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature.git

commit ca4b9b47c9c5f4895ee9add2f4f8bd6308d2cd7a
Author: Karl Pauls <ka...@gmail.com>
AuthorDate: Wed Jan 15 13:40:48 2020 +0100

    SLING-8998: Add feature-origins metadata entry to artifacts in features when merging
---
 .../apache/sling/feature/builder/BuilderUtil.java  |  72 ++++++++++--
 .../sling/feature/builder/FeatureBuilder.java      |   9 ++
 .../sling/feature/builder/BuilderUtilTest.java     |  11 ++
 .../sling/feature/builder/FeatureBuilderTest.java  | 128 +++++++++++++--------
 4 files changed, 165 insertions(+), 55 deletions(-)

diff --git a/src/main/java/org/apache/sling/feature/builder/BuilderUtil.java b/src/main/java/org/apache/sling/feature/builder/BuilderUtil.java
index febf000..0651243 100644
--- a/src/main/java/org/apache/sling/feature/builder/BuilderUtil.java
+++ b/src/main/java/org/apache/sling/feature/builder/BuilderUtil.java
@@ -19,6 +19,7 @@ package org.apache.sling.feature.builder;
 import java.io.StringReader;
 import java.io.StringWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashMap;
@@ -166,7 +167,7 @@ class BuilderUtil {
                     selectedArtifacts.add(count++, existing);
                     selectedArtifacts.add(artifactFromSource);
                 } else {
-                    List<Artifact> artifacts = selectArtifactOverride(existing, artifactFromSource, artifactOverrides);
+                    List<Artifact> artifacts = selectArtifactOverride(existing, artifactFromSource, artifactOverrides, sourceFeature.getId().toMvnId());
                     // if we have an all policy we might have more then one artifact - we put the target one first
                     if (artifacts.size() > 1) {
                         selectedArtifacts.add(count++, artifacts.remove(0));
@@ -187,7 +188,7 @@ class BuilderUtil {
 
             for (final Artifact sa : new LinkedHashSet<>(selectedArtifacts)) {
                 // create a copy to detach artifact from source
-                final Artifact cp = sa.copy(sa.getId());
+                final Artifact cp = addFeatureOrigin(sourceFeature, sa.copy(sa.getId()));
                 // Record the original feature of the bundle, if needed
                 if (originKey != null) {
                     if (sourceFeature != null && source.contains(sa) && sa.getMetadata().get(originKey) == null) {
@@ -206,10 +207,15 @@ class BuilderUtil {
     }
 
     static List<Artifact> selectArtifactOverride(Artifact fromTarget, Artifact fromSource,
-            List<ArtifactId> artifactOverrides) {
+        List<ArtifactId> artifactOverrides) {
+        return selectArtifactOverride(fromTarget, fromSource, artifactOverrides, null);
+    }
+
+    static List<Artifact> selectArtifactOverride(Artifact fromTarget, Artifact fromSource,
+            List<ArtifactId> artifactOverrides, String sourceID) {
         if (fromTarget.getId().equals(fromSource.getId())) {
             // They're the same so return the source (latest)
-            return Collections.singletonList(selectStartOrder(fromTarget, fromSource, fromSource));
+            return Collections.singletonList(addFeatureOrigin(selectStartOrder(fromTarget, fromSource, fromSource), fromTarget, fromSource));
         }
 
         final Set<ArtifactId> commonPrefixes = getCommonArtifactIds(fromTarget, fromSource);
@@ -219,6 +225,11 @@ class BuilderUtil {
         }
 
         final List<Artifact> result = new ArrayList<>();
+        if (artifactOverrides.isEmpty() && getFeatureOrigins(fromTarget).contains(sourceID)) {
+            result.add(fromTarget);
+            result.add(addFeatureOrigin(fromSource, fromTarget, fromSource));
+            return result;
+        }
         outer: for (ArtifactId prefix : commonPrefixes) {
             for (final ArtifactId override : artifactOverrides) {
                 if (match(prefix, override)) {
@@ -230,21 +241,24 @@ class BuilderUtil {
                     } else if (BuilderContext.VERSION_OVERRIDE_HIGHEST.equalsIgnoreCase(rule)) {
                         Version a1v = fromTarget.getId().getOSGiVersion();
                         Version a2v = fromSource.getId().getOSGiVersion();
-                        result.add(selectStartOrder(fromTarget, fromSource, a1v.compareTo(a2v) > 0 ? fromTarget : fromSource));
+                        result.add(
+                            addFeatureOrigin(
+                                selectStartOrder(fromTarget, fromSource, a1v.compareTo(a2v) > 0 ? fromTarget : fromSource),
+                                fromTarget, fromSource));
                     } else if (BuilderContext.VERSION_OVERRIDE_LATEST.equalsIgnoreCase(rule)) {
-                        result.add(selectStartOrder(fromTarget, fromSource, fromSource));
+                        result.add(addFeatureOrigin(selectStartOrder(fromTarget, fromSource, fromSource), fromTarget, fromSource));
                     } else {
 
                         // The rule must represent a version
                         // See if its one of the existing artifact. If so use those, as they may have
                         // additional metadata
                         if (fromTarget.getId().getVersion().equals(rule)) {
-                            result.add(selectStartOrder(fromTarget, fromSource, fromTarget));
+                            result.add(addFeatureOrigin(selectStartOrder(fromTarget, fromSource, fromTarget), fromTarget, fromSource));
                         } else if (fromSource.getId().getVersion().equals(rule)) {
-                            result.add(selectStartOrder(fromTarget, fromSource, fromSource));
+                            result.add(addFeatureOrigin(selectStartOrder(fromTarget, fromSource, fromSource), fromTarget, fromSource));
                         } else {
                             // It's a completely new artifact
-                            result.add(selectStartOrder(fromTarget, fromSource, new Artifact(override)));
+                            result.add(addFeatureOrigin(selectStartOrder(fromTarget, fromSource, new Artifact(override)), fromTarget, fromSource));
                         }
                     }
                     break outer;
@@ -282,6 +296,46 @@ class BuilderUtil {
         }
     }
 
+    private static Artifact addFeatureOrigin(Artifact target, Artifact... artifacts) {
+        return addFeatureOrigin(null, target, artifacts);
+    }
+
+    private static Artifact addFeatureOrigin(Feature feature, Artifact target, Artifact... artifacts) {
+        LinkedHashSet<String> originFeatures = new LinkedHashSet<>();
+        if (artifacts != null && artifacts.length > 0) {
+            for (Artifact artifact : artifacts) {
+                originFeatures.addAll(getFeatureOrigins(artifact));
+            }
+        }
+        else {
+            originFeatures.addAll(getFeatureOrigins(target));
+        }
+        if (feature != null) {
+            originFeatures.add(feature.getId().toMvnId());
+        }
+        String origins = String.join(",", originFeatures);
+        if (origins.equals(target.getMetadata().get("feature-origins"))) {
+            return target;
+        }
+        else {
+            Artifact result = target.copy(target.getId());
+            result.getMetadata().put("feature-origins", origins);
+            return result;
+        }
+    }
+
+    private static LinkedHashSet<String> getFeatureOrigins(Artifact artifact) {
+        String origins = artifact.getMetadata().get("feature-origins");
+        LinkedHashSet<String> originFeatures;
+        if (origins == null) {
+            originFeatures = new LinkedHashSet<>();
+        }
+        else {
+            originFeatures =  new LinkedHashSet<>(Arrays.asList(origins.split(",")));
+        }
+        return originFeatures;
+    }
+
     private static boolean match(final ArtifactId id, final ArtifactId override) {
         int matchCount = 0;
         // check group id
diff --git a/src/main/java/org/apache/sling/feature/builder/FeatureBuilder.java b/src/main/java/org/apache/sling/feature/builder/FeatureBuilder.java
index 83de762..5566e0f 100644
--- a/src/main/java/org/apache/sling/feature/builder/FeatureBuilder.java
+++ b/src/main/java/org/apache/sling/feature/builder/FeatureBuilder.java
@@ -17,9 +17,11 @@
 package org.apache.sling.feature.builder;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -334,6 +336,13 @@ public abstract class FeatureBuilder {
 
             for (Artifact a : result.getBundles()) {
                 a.getMetadata().remove(TRACKING_KEY);
+                String origins = a.getMetadata().get("feature-origins");
+                if (origins != null && !origins.isEmpty()) {
+                    LinkedHashSet<String> originList = new LinkedHashSet<>(Arrays.asList(origins.split(",")));
+                    originList.remove(prototypeFeature.getId().toMvnId());
+                    originList.add(feature.getId().toMvnId());
+                    a.getMetadata().put("feature-origins", String.join(",", originList));
+                }
             }
             for (Extension e : result.getExtensions()) {
                 if (ExtensionType.ARTIFACTS == e.getType()) {
diff --git a/src/test/java/org/apache/sling/feature/builder/BuilderUtilTest.java b/src/test/java/org/apache/sling/feature/builder/BuilderUtilTest.java
index e4f41a7..b132e99 100644
--- a/src/test/java/org/apache/sling/feature/builder/BuilderUtilTest.java
+++ b/src/test/java/org/apache/sling/feature/builder/BuilderUtilTest.java
@@ -790,4 +790,15 @@ public class BuilderUtilTest {
 
         return a;
     }
+
+    @SafeVarargs
+    public static Artifact createBundle(final String id, Map.Entry<String, String> ... metadata) {
+        final Artifact a = new Artifact(ArtifactId.parse(id));
+
+        for (Map.Entry<String, String> md : metadata) {
+            a.getMetadata().put(md.getKey(), md.getValue());
+        }
+
+        return a;
+    }
 }
diff --git a/src/test/java/org/apache/sling/feature/builder/FeatureBuilderTest.java b/src/test/java/org/apache/sling/feature/builder/FeatureBuilderTest.java
index 53ed1ce..a5ec305 100644
--- a/src/test/java/org/apache/sling/feature/builder/FeatureBuilderTest.java
+++ b/src/test/java/org/apache/sling/feature/builder/FeatureBuilderTest.java
@@ -23,6 +23,7 @@ import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import java.util.AbstractMap;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
@@ -211,7 +212,7 @@ public class FeatureBuilderTest {
         b.getBundles().add(BuilderUtilTest.createBundle("o/a/6.0.0", 10));
 
         Feature ab = new Feature(ArtifactId.fromMvnId("g:ab:1"));
-        ab.getBundles().add(BuilderUtilTest.createBundle("o/a/6.0.0", 8));
+        ab.getBundles().add(BuilderUtilTest.createBundle("o/a/6.0.0", 8, new AbstractMap.SimpleEntry("feature-origins", a.getId() + "," + b.getId())));
 
         Feature assembled = FeatureBuilder.assemble(ArtifactId.fromMvnId("g:ab:1"), new BuilderContext(provider)
                 .addArtifactsOverride(ArtifactId.fromMvnId("o:a:HIGHEST")), a, b);
@@ -219,9 +220,6 @@ public class FeatureBuilderTest {
 
         equals(ab, assembled );
 
-        ab = new Feature(ArtifactId.fromMvnId("g:ab:1"));
-        ab.getBundles().add(BuilderUtilTest.createBundle("o/a/6.0.0", 8));
-
         assembled = FeatureBuilder.assemble(ArtifactId.fromMvnId("g:ab:1"), new BuilderContext(provider)
             .addArtifactsOverride(ArtifactId.fromMvnId("o:a:LATEST")), a, b);
         assembled.getExtensions().clear();
@@ -229,8 +227,12 @@ public class FeatureBuilderTest {
         equals(ab, assembled );
 
         ab = new Feature(ArtifactId.fromMvnId("g:ab:1"));
-        ab.getBundles().addAll(a.getBundles());
-        ab.getBundles().addAll(b.getBundles());
+        for (Artifact bundle : a.getBundles()) {
+            ab.getBundles().add(BuilderUtilTest.createBundle(bundle.getId().toMvnId(), bundle.getStartOrder(), new AbstractMap.SimpleEntry("feature-origins", a.getId() + "," + b.getId())));
+        }
+        for (Artifact bundle : b.getBundles()) {
+            ab.getBundles().add(BuilderUtilTest.createBundle(bundle.getId().toMvnId(), bundle.getStartOrder(), new AbstractMap.SimpleEntry("feature-origins", b.getId().toMvnId())));
+        }
 
         assembled = FeatureBuilder.assemble(ArtifactId.fromMvnId("g:ab:1"), new BuilderContext(provider)
             .addArtifactsOverride(ArtifactId.fromMvnId("o:a:ALL")), a, b);
@@ -242,7 +244,7 @@ public class FeatureBuilderTest {
         a.getBundles().get(1).setStartOrder(1);
 
         ab = new Feature(ArtifactId.fromMvnId("g:ab:1"));
-        ab.getBundles().add(BuilderUtilTest.createBundle("o/a/6.0.0", 1));
+        ab.getBundles().add(BuilderUtilTest.createBundle("o/a/6.0.0", 1, new AbstractMap.SimpleEntry("feature-origins", a.getId() + "," + b.getId())));
         ab.getBundles().get(0).setStartOrder(1);
 
         assembled = FeatureBuilder.assemble(ArtifactId.fromMvnId("g:ab:1"), new BuilderContext(provider)
@@ -253,6 +255,35 @@ public class FeatureBuilderTest {
         equals(ab, assembled);
     }
 
+    @Test public void testMergeMultipleVersionsNoConflict() {
+        Feature a = new Feature(ArtifactId.fromMvnId("g:a:1"));
+        Feature b = new Feature(ArtifactId.fromMvnId("g:b:1"));
+
+
+        a.getBundles().add(BuilderUtilTest.createBundle("o/a/1.0.0", 10));
+        a.getBundles().add(BuilderUtilTest.createBundle("o/a/2.0.0", 9));
+        a.getBundles().add(BuilderUtilTest.createBundle("o/a/3.0.0", 11));
+
+        b.getBundles().add(BuilderUtilTest.createBundle("o/b    /4.0.0", 8));
+        b.getBundles().add(BuilderUtilTest.createBundle("o/b/5.0.0", 12));
+        b.getBundles().add(BuilderUtilTest.createBundle("o/b/6.0.0", 10));
+
+        Feature ab = new Feature(ArtifactId.fromMvnId("g:ab:1"));
+        ab.getBundles().add(BuilderUtilTest.createBundle("o/a/1.0.0", 10, new AbstractMap.SimpleEntry("feature-origins", a.getId().toMvnId())));
+        ab.getBundles().add(BuilderUtilTest.createBundle("o/a/2.0.0", 9, new AbstractMap.SimpleEntry("feature-origins", a.getId().toMvnId())));
+        ab.getBundles().add(BuilderUtilTest.createBundle("o/a/3.0.0", 11, new AbstractMap.SimpleEntry("feature-origins", a.getId().toMvnId())));
+
+        ab.getBundles().add(BuilderUtilTest.createBundle("o/b/4.0.0", 8, new AbstractMap.SimpleEntry("feature-origins", b.getId().toMvnId())));
+        ab.getBundles().add(BuilderUtilTest.createBundle("o/b/5.0.0", 12, new AbstractMap.SimpleEntry("feature-origins", b.getId().toMvnId())));
+        ab.getBundles().add(BuilderUtilTest.createBundle("o/b/6.0.0", 10, new AbstractMap.SimpleEntry("feature-origins", b.getId().toMvnId())));
+
+
+        Feature assembled = FeatureBuilder.assemble(ArtifactId.fromMvnId("g:ab:1"), new BuilderContext(provider), a, b);
+        assembled.getExtensions().clear();
+
+        equals(ab, assembled);
+    }
+
 
     @Test public void testNoIncludesNoUpgrade() throws Exception {
         final Feature base = new Feature(ArtifactId.parse("org.apache.sling/test-feature/1.1"));
@@ -353,18 +384,20 @@ public class FeatureBuilderTest {
         result.setPrototype(null);
         result.getBundles().clear();
 
-        result.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/foo-bar/4.5.6", 3));
-        result.getBundles().add(BuilderUtilTest.createBundle("group/testnewversion_low/2", 5));
-        result.getBundles().add(BuilderUtilTest.createBundle("group/testnewversion_low/1", 5));
-        result.getBundles().add(BuilderUtilTest.createBundle("group/testnewversion_high/2", 5));
-        result.getBundles().add(BuilderUtilTest.createBundle("group/testnewversion_high/5", 5));
-        result.getBundles().add(BuilderUtilTest.createBundle("group/testnewstartlevel/1", 5));
-        result.getBundles().add(BuilderUtilTest.createBundle("group/testnewstartlevelandversion/1", 5));
-        result.getBundles().add(BuilderUtilTest.createBundle("group/testnewstartlevelandversion/2", 10));
-        result.getBundles().add(a1.copy(a1.getId()));
-        result.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/application-bundle/2.0.0", 1));
-        result.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/another-bundle/2.1.0", 1));
-        result.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/foo-xyz/1.2.3", 2));
+        result.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/foo-bar/4.5.6", 3, new AbstractMap.SimpleEntry("feature-origins", base.getId().toMvnId())));
+        result.getBundles().add(BuilderUtilTest.createBundle("group/testnewversion_low/2", 5, new AbstractMap.SimpleEntry("feature-origins", base.getId().toMvnId())));
+        result.getBundles().add(BuilderUtilTest.createBundle("group/testnewversion_low/1", 5, new AbstractMap.SimpleEntry("feature-origins", base.getId().toMvnId())));
+        result.getBundles().add(BuilderUtilTest.createBundle("group/testnewversion_high/2", 5, new AbstractMap.SimpleEntry("feature-origins", base.getId().toMvnId())));
+        result.getBundles().add(BuilderUtilTest.createBundle("group/testnewversion_high/5", 5, new AbstractMap.SimpleEntry("feature-origins", base.getId().toMvnId())));
+        result.getBundles().add(BuilderUtilTest.createBundle("group/testnewstartlevel/1", 5, new AbstractMap.SimpleEntry("feature-origins", base.getId().toMvnId())));
+        result.getBundles().add(BuilderUtilTest.createBundle("group/testnewstartlevelandversion/1", 5, new AbstractMap.SimpleEntry("feature-origins",base.getId().toMvnId())));
+        result.getBundles().add(BuilderUtilTest.createBundle("group/testnewstartlevelandversion/2", 10, new AbstractMap.SimpleEntry("feature-origins", base.getId().toMvnId())));
+        Artifact copy = a1.copy(a1.getId());
+        copy.getMetadata().put("feature-origins", base.getId().toMvnId());
+        result.getBundles().add(copy);
+        result.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/application-bundle/2.0.0", 1, new AbstractMap.SimpleEntry("feature-origins", base.getId().toMvnId())));
+        result.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/another-bundle/2.1.0", 1, new AbstractMap.SimpleEntry("feature-origins", base.getId().toMvnId())));
+        result.getBundles().add(BuilderUtilTest.createBundle("org.apache.sling/foo-xyz/1.2.3", 2, new AbstractMap.SimpleEntry("feature-origins", base.getId().toMvnId())));
 
         result.getVariables().put("varx", "myvalx");
         result.getFrameworkProperties().put("bar", "X");
@@ -392,24 +425,34 @@ public class FeatureBuilderTest {
 
         Feature result = new Feature(ArtifactId.parse("g:tgtart:1"));
 
-        result.getBundles().add(BuilderUtilTest.createBundle("group/testmulti/2", 8));
-
-
-        Artifact b1 = new Artifact(ArtifactId.fromMvnId("group:testmulti:1"));
+        result.getBundles().add(BuilderUtilTest.createBundle("group/testmulti/2", 8, new AbstractMap.SimpleEntry("feature-origins", base.getId().toMvnId())));
+        Artifact b1 = BuilderUtilTest.createBundle("group:testmulti:1", new AbstractMap.SimpleEntry("feature-origins", base.getId().toMvnId()));
         result.getBundles().add(b1);
 
 
-        Artifact b2 = new Artifact(ArtifactId.fromMvnId("group:testmulti:3"));
+        Artifact b2 = BuilderUtilTest.createBundle("group:testmulti:3", new AbstractMap.SimpleEntry("feature-origins", base.getId().toMvnId()));
         result.getBundles().add(b2);
 
-        Artifact b3 = new Artifact(ArtifactId.fromMvnId("group:someart:1.2.3"));
+        Artifact b3 = BuilderUtilTest.createBundle("group:someart:1.2.3", new AbstractMap.SimpleEntry("feature-origins", base.getId().toMvnId()));
         b3.setStartOrder(4);
         result.getBundles().add(b3);
-        Artifact b0 = new Artifact(ArtifactId.fromMvnId("g:myart:1"));
+        Artifact b0 = BuilderUtilTest.createBundle("g:myart:1", new AbstractMap.SimpleEntry("feature-origins", base.getId().toMvnId()));
         result.getBundles().add(b0);
 
 
         equals(result, assembled);
+
+        Feature addOn = new Feature(ArtifactId.fromMvnId("g:addon:1"));
+        addOn.getBundles().add(BuilderUtilTest.createBundle("group:someart:1.2.3"));
+        assembled = FeatureBuilder.assemble(ArtifactId.fromMvnId("g:tgtart:2"), builderContext, assembled, addOn);
+        assembled.getExtensions().clear();
+
+        result = result.copy(ArtifactId.fromMvnId("g:tgtart:2"));
+        int idx = result.getBundles().indexOf(BuilderUtilTest.createBundle("group:someart:1.2.3"));
+        result.getBundles().remove(idx);
+        result.getBundles().add(idx, BuilderUtilTest.createBundle("group:someart:1.2.3", 4, new AbstractMap.SimpleEntry("feature-origins", base.getId().toMvnId() + "," + addOn.getId())));
+
+        equals(result, assembled);
     }
 
     @Test public void testSingleIncludeMultiVersion2() {
@@ -422,16 +465,13 @@ public class FeatureBuilderTest {
         Feature assembled = FeatureBuilder.assemble(base, builderContext);
 
         Feature result = new Feature(ArtifactId.parse("g:tgtart:1"));
-        Artifact b1 = new Artifact(ArtifactId.fromMvnId("group:testmulti:1"));
-        b1.setStartOrder(4);
+        Artifact b1 = BuilderUtilTest.createBundle("group:testmulti:1" ,4, new AbstractMap.SimpleEntry("feature-origins", base.getId().toMvnId()));
         result.getBundles().add(b1);
-        Artifact b2 = new Artifact(ArtifactId.fromMvnId("group:testmulti:2"));
-        b2.setStartOrder(8);
+        Artifact b2 = BuilderUtilTest.createBundle("group:testmulti:2",8, new AbstractMap.SimpleEntry("feature-origins", base.getId().toMvnId()));
         result.getBundles().add(b2);
-        Artifact b3 = new Artifact(ArtifactId.fromMvnId("group:someart:1.2.3"));
-        b3.setStartOrder(4);
+        Artifact b3 = BuilderUtilTest.createBundle("group:someart:1.2.3",4, new AbstractMap.SimpleEntry("feature-origins", base.getId().toMvnId()));
         result.getBundles().add(b3);
-        Artifact b0 = new Artifact(ArtifactId.fromMvnId("g:myart:1"));
+        Artifact b0 = BuilderUtilTest.createBundle("g:myart:1", new AbstractMap.SimpleEntry("feature-origins", base.getId().toMvnId()));
         result.getBundles().add(b0);
 
         equals(result, assembled);
@@ -449,15 +489,13 @@ public class FeatureBuilderTest {
 
         Feature result = new Feature(ArtifactId.parse("g:tgtart:1"));
 
-        result.getBundles().add(BuilderUtilTest.createBundle("group/testmulti/2", 8));
+        result.getBundles().add(BuilderUtilTest.createBundle("group/testmulti/2", 8, new AbstractMap.SimpleEntry("feature-origins", base.getId().toMvnId())));
 
-        Artifact b1 = new Artifact(ArtifactId.fromMvnId("group:testmulti:1"));
-        b1.setStartOrder(4);
+        Artifact b1 = BuilderUtilTest.createBundle("group:testmulti:1", 4, new AbstractMap.SimpleEntry("feature-origins", base.getId().toMvnId()));
         result.getBundles().add(b1);
-        Artifact b3 = new Artifact(ArtifactId.fromMvnId("group:someart:1.2.3"));
-        b3.setStartOrder(4);
+        Artifact b3 = BuilderUtilTest.createBundle("group:someart:1.2.3", 4, new AbstractMap.SimpleEntry("feature-origins", base.getId().toMvnId()));
         result.getBundles().add(b3);
-        Artifact b0 = new Artifact(ArtifactId.fromMvnId("g:myart:1"));
+        Artifact b0 = BuilderUtilTest.createBundle("g:myart:1", new AbstractMap.SimpleEntry("feature-origins", base.getId().toMvnId()));
         result.getBundles().add(b0);
 
         equals(result, assembled);
@@ -476,19 +514,17 @@ public class FeatureBuilderTest {
 
         Feature result = new Feature(ArtifactId.parse("g:tgtart:1"));
 
-        result.getBundles().add(BuilderUtilTest.createBundle("group/testmulti/2", 8));
+        result.getBundles().add(BuilderUtilTest.createBundle("group/testmulti/2", 8, new AbstractMap.SimpleEntry("feature-origins", base.getId().toMvnId())));
 
-        Artifact b1 = new Artifact(ArtifactId.fromMvnId("group:testmulti:1"));
-        b1.setStartOrder(4);
+        Artifact b1 = BuilderUtilTest.createBundle("group:testmulti:1",4, new AbstractMap.SimpleEntry("feature-origins", base.getId().toMvnId()));
         result.getBundles().add(b1);
 
-        Artifact b2 = new Artifact(ArtifactId.fromMvnId("group:testmulti:3"));
+        Artifact b2 = BuilderUtilTest.createBundle("group:testmulti:3", new AbstractMap.SimpleEntry("feature-origins", base.getId().toMvnId()));
         result.getBundles().add(b2);
 
-        Artifact b3 = new Artifact(ArtifactId.fromMvnId("group:someart:1.2.3"));
-        b3.setStartOrder(4);
+        Artifact b3 = BuilderUtilTest.createBundle("group:someart:1.2.3", 4, new AbstractMap.SimpleEntry("feature-origins", base.getId().toMvnId()));
         result.getBundles().add(b3);
-        Artifact b0 = new Artifact(ArtifactId.fromMvnId("g:myart:1"));
+        Artifact b0 = BuilderUtilTest.createBundle("g:myart:1", new AbstractMap.SimpleEntry("feature-origins", base.getId().toMvnId()));
         result.getBundles().add(b0);
 
         equals(result, assembled);