Bug 544838 - Unify and generalize test bundle/feature creation

Change-Id: Ie2f13fb0350681a027700ad060b3d801d639a277
Signed-off-by: Hannes Wellmann <wellmann.hannes1@gmx.net>
Reviewed-on: https://git.eclipse.org/r/c/pde/eclipse.pde.ui/+/192272
Tested-by: PDE Bot <pde-bot@eclipse.org>
diff --git a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/core/tests/internal/DependencyManagerTest.java b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/core/tests/internal/DependencyManagerTest.java
index a0f64dc..e55c3c0 100644
--- a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/core/tests/internal/DependencyManagerTest.java
+++ b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/core/tests/internal/DependencyManagerTest.java
@@ -21,6 +21,10 @@
 import static org.eclipse.pde.internal.core.DependencyManager.Options.INCLUDE_ALL_FRAGMENTS;
 import static org.eclipse.pde.internal.core.DependencyManager.Options.INCLUDE_NON_TEST_FRAGMENTS;
 import static org.eclipse.pde.internal.core.DependencyManager.Options.INCLUDE_OPTIONAL_DEPENDENCIES;
+import static org.eclipse.pde.ui.tests.util.TargetPlatformUtil.bundle;
+import static org.eclipse.pde.ui.tests.util.TargetPlatformUtil.bundleVersion;
+import static org.eclipse.pde.ui.tests.util.TargetPlatformUtil.resolution;
+import static org.eclipse.pde.ui.tests.util.TargetPlatformUtil.version;
 import static org.osgi.framework.Constants.EXPORT_PACKAGE;
 import static org.osgi.framework.Constants.FRAGMENT_HOST;
 import static org.osgi.framework.Constants.IMPORT_PACKAGE;
@@ -31,7 +35,6 @@
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.*;
-import java.util.Map.Entry;
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.jdt.core.IClasspathEntry;
@@ -210,7 +213,7 @@
 
 		Set<BundleDescription> allFragmentsClosure = findRequirementsClosure(bundles, INCLUDE_ALL_FRAGMENTS);
 		assertThat(allFragmentsClosure)
-				.isEqualTo(Set.of(bundleA, bundleFragment, bundleFragmentWithDeps, bundleB, bundleC));
+		.isEqualTo(Set.of(bundleA, bundleFragment, bundleFragmentWithDeps, bundleB, bundleC));
 	}
 
 	@Test
@@ -284,26 +287,8 @@
 
 	@SafeVarargs
 	private void setTargetPlatform(Map.Entry<NameVersionDescriptor, Map<String, String>>... pluginDescriptions)
-			throws IOException, InterruptedException {
-		TargetPlatformUtil.setDummyBundlesAsTarget(Map.ofEntries(pluginDescriptions), tpJarDirectory);
-	}
-
-	@SafeVarargs
-	private static Entry<NameVersionDescriptor, Map<String, String>> bundle(String id, String version,
-			Entry<String, String>... additionalManifestEntries) {
-		return entry(new NameVersionDescriptor(id, version), Map.ofEntries(additionalManifestEntries));
-	}
-
-	private static String version(String version) {
-		return ";" + Constants.VERSION_ATTRIBUTE + "=\"" + version + "\"";
-	}
-
-	private static String bundleVersion(String lowerBound, String upperBound) {
-		return ";" + Constants.BUNDLE_VERSION_ATTRIBUTE + "=\"[" + lowerBound + "," + upperBound + ")\"";
-	}
-
-	private static String resolution(String resolutionType) {
-		return ";" + Constants.RESOLUTION_DIRECTIVE + ":=\"" + resolutionType + "\"";
+			throws Exception {
+		TargetPlatformUtil.setDummyBundlesAsTarget(Map.ofEntries(pluginDescriptions), List.of(), tpJarDirectory);
 	}
 
 	private static final String OPTIONAL = Constants.RESOLUTION_OPTIONAL;
diff --git a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/classpathresolver/ClasspathResolverTest.java b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/classpathresolver/ClasspathResolverTest.java
index 05bfc3b..c94734f 100644
--- a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/classpathresolver/ClasspathResolverTest.java
+++ b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/classpathresolver/ClasspathResolverTest.java
@@ -18,6 +18,7 @@
 
 import static org.eclipse.pde.ui.tests.launcher.AbstractLaunchTest.findTargetModel;
 import static org.eclipse.pde.ui.tests.launcher.AbstractLaunchTest.findWorkspaceModel;
+import static org.eclipse.pde.ui.tests.util.TargetPlatformUtil.bundle;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 
@@ -27,6 +28,7 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.*;
+import java.util.Map.Entry;
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.runtime.*;
 import org.eclipse.debug.core.sourcelookup.ISourceContainer;
@@ -72,9 +74,9 @@
 		project = ProjectUtils.importTestProject("tests/projects/" + bundleName);
 		// create workspace plug-ins with same id like a running-platform bundle
 		Bundle hostBundle = Platform.getBundle(HOST_BUNDLE_ID);
-		createWorkspacePluginProjects(List.of( //
+		createWorkspacePluginProjects( //
 				bundle(hostBundle.getSymbolicName(), "2.0.0"), //
-				bundle(hostBundle.getSymbolicName(), hostBundle.getVersion().toString())));
+				bundle(hostBundle.getSymbolicName(), hostBundle.getVersion().toString()));
 	}
 
 	@Rule
@@ -113,7 +115,7 @@
 	 */
 	@Test
 	public void testGetDevProperties() throws Exception {
-		mockTPWithRunningPlatformAndBundles(List.of()); // running-platform only
+		mockTPWithRunningPlatformAndBundles(); // running-platform only
 
 		File devProperties = tempFolder.newFile("dev.properties").getCanonicalFile();
 		String devPropertiesURL = ClasspathHelper.getDevEntriesProperties(devProperties.getPath(), false);
@@ -130,7 +132,7 @@
 	 */
 	@Test
 	public void testSourceLookupPath() throws Exception {
-		mockTPWithRunningPlatformAndBundles(List.of()); // running-platform only
+		mockTPWithRunningPlatformAndBundles(); // running-platform only
 
 		PDESourceLookupDirector d = new PDESourceLookupDirector();
 		_PDESourceLookupQuery q = new _PDESourceLookupQuery(d, project);
@@ -148,7 +150,7 @@
 
 		getHostBundleAndMockDevProperties();
 
-		mockTPWithBundles(List.of()); // empty TP
+		mockTPWithBundles(); // empty TP
 
 		IPluginModelBase wsModel = findWorkspaceModel(HOST_BUNDLE_ID, "2.0.0");
 
@@ -167,7 +169,7 @@
 		Bundle hostBundle = getHostBundleAndMockDevProperties();
 		String hostBundleVersion = hostBundle.getVersion().toString();
 
-		mockTPWithBundles(List.of()); // empty TP
+		mockTPWithBundles(); // empty TP
 
 		IPluginModelBase wsModel = findWorkspaceModel(HOST_BUNDLE_ID, hostBundleVersion);
 
@@ -185,7 +187,7 @@
 		Bundle hostBundle = getHostBundleAndMockDevProperties();
 		String hostBundleVersion = hostBundle.getVersion().toString();
 
-		mockTPWithRunningPlatformAndBundles(List.of()); // running-platform only
+		mockTPWithRunningPlatformAndBundles(); // running-platform only
 
 		IPluginModelBase hostModel = findTargetModel(HOST_BUNDLE_ID, hostBundleVersion);
 
@@ -204,8 +206,8 @@
 
 		// pretend there is only a jar-bundle in the TP that has the same
 		// name and version like a woven plug-in from the host
-		mockTPWithBundles(List.of( //
-				bundle(HOST_BUNDLE_ID, "1.0.0")));
+		mockTPWithBundles( //
+				bundle(HOST_BUNDLE_ID, "1.0.0"));
 
 		IPluginModelBase tpModel = findTargetModel(HOST_BUNDLE_ID, "1.0.0");
 
@@ -224,8 +226,8 @@
 
 		// pretend there is only a jar-bundle in the TP that has the same
 		// name and version like a woven plug-in from the host
-		mockTPWithBundles(List.of( //
-				bundle(HOST_BUNDLE_ID, hostBundleVersion)));
+		mockTPWithBundles(//
+				bundle(HOST_BUNDLE_ID, hostBundleVersion));
 
 		IPluginModelBase hostModel = findTargetModel(HOST_BUNDLE_ID, hostBundleVersion);
 
@@ -242,8 +244,8 @@
 
 		getHostBundleAndMockDevProperties();
 
-		mockTPWithBundles(List.of( //
-				bundle(HOST_BUNDLE_ID, "1.0.0")));
+		mockTPWithBundles( //
+				bundle(HOST_BUNDLE_ID, "1.0.0"));
 
 		IPluginModelBase hostModel = findTargetModel(HOST_BUNDLE_ID, "1.0.0");
 		IPluginModelBase wsModel = findWorkspaceModel(HOST_BUNDLE_ID, "2.0.0");
@@ -263,8 +265,8 @@
 		Bundle hostBundle = getHostBundleAndMockDevProperties();
 		String hostBundleVersion = hostBundle.getVersion().toString();
 
-		mockTPWithRunningPlatformAndBundles(List.of( //
-				bundle(HOST_BUNDLE_ID, "1.0.0")));
+		mockTPWithRunningPlatformAndBundles( //
+				bundle(HOST_BUNDLE_ID, "1.0.0"));
 
 		IPluginModelBase hostModel = findTargetModel(HOST_BUNDLE_ID, hostBundleVersion);
 		IPluginModelBase tpModel = findTargetModel(HOST_BUNDLE_ID, "1.0.0");
@@ -284,7 +286,7 @@
 		Bundle hostBundle = getHostBundleAndMockDevProperties();
 		String hostBundleVersion = hostBundle.getVersion().toString();
 
-		mockTPWithRunningPlatformAndBundles(List.of()); // running-platform only
+		mockTPWithRunningPlatformAndBundles(); // running-platform only
 
 		IPluginModelBase hostModel = findTargetModel(HOST_BUNDLE_ID, hostBundleVersion);
 		IPluginModelBase wsModel = findWorkspaceModel(HOST_BUNDLE_ID, "2.0.0");
@@ -304,8 +306,8 @@
 		Bundle hostBundle = getHostBundleAndMockDevProperties();
 		String hostBundleVersion = hostBundle.getVersion().toString();
 
-		mockTPWithRunningPlatformAndBundles(List.of( //
-				bundle(HOST_BUNDLE_ID, "1.0.0")));
+		mockTPWithRunningPlatformAndBundles( //
+				bundle(HOST_BUNDLE_ID, "1.0.0"));
 
 		IPluginModelBase hostModel = findTargetModel(HOST_BUNDLE_ID, hostBundleVersion);
 		IPluginModelBase tpModel = findTargetModel(HOST_BUNDLE_ID, "1.0.0");
@@ -339,9 +341,11 @@
 		return oldValue;
 	}
 
-	private static void createWorkspacePluginProjects(List<NameVersionDescriptor> workspacePlugins)
-			throws CoreException {
-		List<IProject> pluginProjects = ProjectUtils.createWorkspacePluginProjects(workspacePlugins);
+	@SafeVarargs
+	private static void createWorkspacePluginProjects(
+			Entry<NameVersionDescriptor, Map<String, String>>... workspacePlugins) throws CoreException {
+		Set<NameVersionDescriptor> descriptions = Map.ofEntries(workspacePlugins).keySet();
+		List<IProject> pluginProjects = ProjectUtils.createWorkspacePluginProjects(descriptions);
 		while (pluginProjects.stream().anyMatch(ClasspathResolverTest::isUpdatePending)) {
 			Thread.yield(); // await async classpath update of projects
 		}
@@ -367,20 +371,18 @@
 		return hostBundle;
 	}
 
-	private void mockTPWithBundles(List<NameVersionDescriptor> targetBundles) throws IOException, InterruptedException {
+	@SafeVarargs
+	private void mockTPWithBundles(Entry<NameVersionDescriptor, Map<String, String>>... bundles) throws Exception {
 		Path jarsDirectory = tempFolder.newFolder("TPJarsDirectory").toPath();
-		TargetPlatformUtil.setDummyBundlesAsTarget(targetBundles, jarsDirectory);
+		TargetPlatformUtil.setDummyBundlesAsTarget(Map.ofEntries(bundles), List.of(), jarsDirectory);
 	}
 
-	private void mockTPWithRunningPlatformAndBundles(List<NameVersionDescriptor> targetBundles)
-			throws IOException, InterruptedException {
+	@SafeVarargs
+	private void mockTPWithRunningPlatformAndBundles(
+			Entry<NameVersionDescriptor, Map<String, String>>... additionalBundles) throws Exception {
 		Path jarsDirectory = tempFolder.newFolder("TPJarsDirectory").toPath();
-		TargetPlatformUtil.setRunningPlatformWithDummyBundlesAsTarget(targetBundles, jarsDirectory,
-				b -> b.getSymbolicName().equals(HOST_BUNDLE_ID));
-	}
-
-	private static NameVersionDescriptor bundle(String id, String version) {
-		return new NameVersionDescriptor(id, version);
+		TargetPlatformUtil.setRunningPlatformWithDummyBundlesAsTarget(b -> b.getSymbolicName().equals(HOST_BUNDLE_ID),
+				Map.ofEntries(additionalBundles), Set.of(), jarsDirectory);
 	}
 
 	private Properties createDevEntryProperties(List<IPluginModelBase> launchedBundles)
diff --git a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/AbstractLaunchTest.java b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/AbstractLaunchTest.java
index 3599d18..7914ce9 100644
--- a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/AbstractLaunchTest.java
+++ b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/AbstractLaunchTest.java
@@ -30,7 +30,6 @@
 import org.eclipse.core.resources.IProject;
 import org.eclipse.debug.core.*;
 import org.eclipse.pde.core.plugin.*;
-import org.eclipse.pde.core.target.NameVersionDescriptor;
 import org.eclipse.pde.ui.tests.util.ProjectUtils;
 import org.eclipse.pde.ui.tests.util.TargetPlatformUtil;
 import org.junit.*;
@@ -87,10 +86,6 @@
 						() -> new NoSuchElementException("No " + type + " model " + id + "-" + version + " found"));
 	}
 
-	static NameVersionDescriptor bundle(String id, String version) {
-		return new NameVersionDescriptor(id, version);
-	}
-
 	static BundleLocationDescriptor workspaceBundle(String id, String version) {
 		Objects.requireNonNull(version);
 		return () -> findWorkspaceModel(id, version);
diff --git a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/FeatureBasedLaunchTest.java b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/FeatureBasedLaunchTest.java
index 5662fa6..f16235f 100644
--- a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/FeatureBasedLaunchTest.java
+++ b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/FeatureBasedLaunchTest.java
@@ -17,9 +17,11 @@
 package org.eclipse.pde.ui.tests.launcher;
 
 import static java.util.Collections.emptySet;
+import static java.util.Map.ofEntries;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.eclipse.pde.internal.core.ICoreConstants.DEFAULT_VERSION;
 import static org.eclipse.pde.ui.tests.util.ProjectUtils.createPluginProject;
+import static org.eclipse.pde.ui.tests.util.TargetPlatformUtil.bundle;
 
 import java.io.PrintWriter;
 import java.nio.file.Files;
@@ -89,7 +91,7 @@
 
 	@Test
 	public void testGetMergedBundleMap_featureSelectionForLocationWorkspace_latestWorkspaceFeature() throws Throwable {
-		List<NameVersionDescriptor> targetBundles = List.of( //
+		var targetBundles = ofEntries( //
 				bundle("plugin.a", "1.0.0"), //
 				bundle("plugin.b", "1.0.0"), //
 				bundle("plugin.c", "1.0.0"), //
@@ -123,7 +125,7 @@
 	@Test
 	public void testGetMergedBundleMap_featureSelectionForLocationWorkspaceButNoWorkspaceFeaturePresent_latestExternalFeature()
 			throws Throwable {
-		List<NameVersionDescriptor> targetBundles = List.of( //
+		var targetBundles = ofEntries( //
 				bundle("plugin.a", "1.0.0"), //
 				bundle("plugin.b", "1.0.0"), //
 				bundle("plugin.c", "1.0.0"));
@@ -151,7 +153,7 @@
 
 	@Test
 	public void testGetMergedBundleMap_featureSelectionForLocationExternal_latestExternalFeature() throws Throwable {
-		List<NameVersionDescriptor> targetBundles = List.of( //
+		var targetBundles = ofEntries( //
 				bundle("plugin.a", "1.0.0"), //
 				bundle("plugin.b", "1.0.0"), //
 				bundle("plugin.c", "1.0.0"), //
@@ -185,7 +187,7 @@
 	@Test
 	public void testGetMergedBundleMap_featureSelectionForLocationExternalButNoExternalFeaturePresent_noFeature()
 			throws Throwable {
-		List<NameVersionDescriptor> targetBundles = List.of( //
+		var targetBundles = ofEntries( //
 				bundle("plugin.a", "1.0.0"));
 
 		createFeatureProject("feature.a", "2.0.0", f -> {
@@ -211,7 +213,7 @@
 		createPluginProject("plugin.a", "1.0.1");
 		createPluginProject("plugin.b", "1.0.0");
 
-		List<NameVersionDescriptor> targetBundles = List.of( //
+		var targetBundles = ofEntries( //
 				bundle("plugin.a", "2.0.0"), //
 				bundle("plugin.a", "2.0.1"), //
 				bundle("plugin.c", "1.0.0"));
@@ -280,7 +282,7 @@
 	public void testGetMergedBundleMap_includedPluginWithSpecificVersion() throws Throwable {
 		createPluginProject("plugin.a", "1.0.0");
 		createPluginProject("plugin.a", "1.2.0");
-		List<NameVersionDescriptor> targetBundles = List.of( //
+		var targetBundles = ofEntries( //
 				bundle("plugin.a", "1.0.0"), //
 				bundle("plugin.a", "1.1.0"), //
 				bundle("plugin.z", "1.0.0"));
@@ -398,7 +400,7 @@
 	public void testGetMergedBundleMap_includedFeatureWithDefaultVersion() throws Throwable {
 		// plug-in names contain version-like suffixes to have no chance to
 		// interfere with conveniences of plug-in resolution
-		List<NameVersionDescriptor> targetBundles = List.of( //
+		var targetBundles = ofEntries( //
 				bundle("plugin.a.e.100", "1.0.0"), //
 				bundle("plugin.a.w.101", "1.0.0"), //
 				bundle("plugin.z", "1.0.0"));
@@ -446,7 +448,7 @@
 	public void testGetMergedBundleMap_includedFeatureWithSpecificVersion() throws Throwable {
 		// plug-in names contain version-like suffixes to have no chance to
 		// interfere with conveniences of plug-in resolution
-		List<NameVersionDescriptor> targetBundles = List.of( //
+		var targetBundles = ofEntries( //
 				bundle("plugin.a.w.100", "1.0.0"), //
 				bundle("plugin.a.w.300", "1.0.0"), //
 				bundle("plugin.a.e.100", "1.0.0"), //
@@ -578,7 +580,7 @@
 		createPluginProject("plugin.a", "1.1.0");
 		createPluginProject("plugin.b", "1.0.0");
 
-		List<NameVersionDescriptor> targetBundles = List.of( //
+		var targetBundles = ofEntries( //
 				bundle("plugin.a", "2.0.0"), //
 				bundle("plugin.a", "2.1.0"), //
 				bundle("plugin.c", "1.0.0"));
@@ -651,7 +653,7 @@
 		createPluginProject("plugin.a", "1.1.2");
 		createPluginProject("plugin.a", "2.0.0");
 
-		List<NameVersionDescriptor> targetBundles = List.of( //
+		var targetBundles = ofEntries( //
 				bundle("plugin.a", "1.0.0"), //
 				bundle("plugin.a", "1.2.3"), //
 				bundle("plugin.a", "3.0.0"), //
@@ -721,7 +723,7 @@
 		createPluginProject("plugin.a", "1.1.2");
 		createPluginProject("plugin.a", "2.0.0");
 
-		List<NameVersionDescriptor> targetBundles = List.of( //
+		var targetBundles = ofEntries( //
 				bundle("plugin.a", "1.0.0"), //
 				bundle("plugin.a", "1.0.2"), //
 				bundle("plugin.a", "1.2.0"), //
@@ -800,7 +802,7 @@
 	public void testGetMergedBundleMap_requiredFeatureWithNoVersion() throws Throwable {
 		// plug-in names contain version-like suffixes to have no chance to
 		// interfere with conveniences of plug-in resolution
-		List<NameVersionDescriptor> targetBundles = List.of( //
+		var targetBundles = ofEntries( //
 				bundle("plugin.a.w.100", "1.0.0"), //
 				bundle("plugin.a.w.110", "1.0.0"), //
 				bundle("plugin.a.e.200", "1.0.0"), //
@@ -882,7 +884,7 @@
 	public void testGetMergedBundleMap_requiredFeatureWithSpecificVersion1() throws Throwable {
 		// plug-in names contain version-like suffixes to have no chance to
 		// interfere with conveniences of plug-in resolution
-		List<NameVersionDescriptor> targetBundles = List.of( //
+		var targetBundles = ofEntries( //
 				bundle("plugin.a.w.100", "1.0.0"), //
 				bundle("plugin.a.w.110", "1.0.0"), //
 				bundle("plugin.a.w.200", "1.0.0"), //
@@ -950,7 +952,7 @@
 
 	@Test
 	public void testGetMergedBundleMap_requiredFeatureWithSpecificVersion2() throws Throwable {
-		List<NameVersionDescriptor> targetBundles = List.of( //
+		var targetBundles = ofEntries( //
 				bundle("plugin.a.w.100", "1.0.0"), //
 				bundle("plugin.a.w.101", "1.0.0"), //
 				bundle("plugin.a.w.110", "1.0.0"), //
@@ -1075,7 +1077,7 @@
 		String thisOS = Platform.getOS();
 		String otherOS = thisOS.equals(Platform.OS_LINUX) ? Platform.OS_WIN32 : Platform.OS_LINUX;
 
-		List<NameVersionDescriptor> targetBundles = List.of( //
+		var targetBundles = ofEntries( //
 				bundle("plugin.a", "1.0.0"), //
 				bundle("plugin.b", "1.0.0"), //
 				bundle("plugin.c", "1.0.0"), //
@@ -1115,7 +1117,7 @@
 		String thisOS = Platform.getOS();
 		String otherOS = thisOS.equals(Platform.OS_LINUX) ? Platform.OS_WIN32 : Platform.OS_LINUX;
 
-		List<NameVersionDescriptor> targetBundles = List.of( //
+		var targetBundles = ofEntries( //
 				bundle("plugin.b", "1.0.0"), //
 				bundle("plugin.b", "1.1.0"), //
 				bundle("plugin.c", "1.0.0"), //
@@ -1160,7 +1162,7 @@
 	@Test
 	public void testGetMergedBundleMap_multipleInclusionOfPluginAndFeature() throws Throwable {
 
-		List<NameVersionDescriptor> targetBundles = List.of( //
+		var targetBundles = ofEntries( //
 				bundle("plugin.a", "1.0.0"), //
 				bundle("plugin.b", "1.0.0"), //
 				bundle("plugin.z", "1.0.0"));
@@ -1208,7 +1210,7 @@
 		createPluginProject("plugin.d", "1.0.0");
 		createPluginProject("plugin.e", "1.0.0");
 
-		List<NameVersionDescriptor> targetBundles = List.of( //
+		var targetBundles = ofEntries( //
 				bundle("plugin.a", "1.0.0"), //
 				bundle("plugin.b", "1.0.0"), //
 				bundle("plugin.c", "1.0.0"), //
@@ -1263,7 +1265,7 @@
 		createPluginProject("plugin.d", "1.0.0");
 		createPluginProject("plugin.e", "1.0.0");
 
-		List<NameVersionDescriptor> targetBundles = List.of( //
+		var targetBundles = ofEntries( //
 				bundle("plugin.a", "1.0.0"), //
 				bundle("plugin.b", "1.0.0"), //
 				bundle("plugin.c", "1.0.0"), //
@@ -1348,8 +1350,8 @@
 		// problem is that the Feature-model is reset/reload while processing
 		// the changes (which first sets all fields to null/zero and then
 		// re-reads them), but the reload is NOT guarded by corresponding locks.
-		// So if this happens asynchronously and the model is read inbetween the model
-		// state could be inconsistent, which occasionally leads to
+		// So if this happens asynchronously and the model is read inbetween the
+		// model state could be inconsistent, which occasionally leads to
 		// test-failure. Furthermore the feature-models are only added to the
 		// FeatureModelManager while processing the resource-changes.
 		// Consequently it has to be ensured that all pending resource change
@@ -1418,9 +1420,9 @@
 		return new NameVersionDescriptor(featureId, featureVersion, NameVersionDescriptor.TYPE_FEATURE);
 	}
 
-	private void setTargetPlatform(List<NameVersionDescriptor> targetPlugins,
+	private void setTargetPlatform(Map<NameVersionDescriptor, Map<String, String>> bundleDescriptions,
 			List<NameVersionDescriptor> targetFeatures) throws Exception {
-		TargetPlatformUtil.setDummyBundlesAsTarget(targetPlugins, tpJarDirectory);
+		TargetPlatformUtil.setDummyBundlesAsTarget(bundleDescriptions, targetFeatures, tpJarDirectory);
 	}
 
 	private static ILaunchConfigurationWorkingCopy createFeatureLaunchConfig() throws CoreException {
diff --git a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/PluginBasedLaunchTest.java b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/PluginBasedLaunchTest.java
index d61e4c9..8cc1837 100644
--- a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/PluginBasedLaunchTest.java
+++ b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/launcher/PluginBasedLaunchTest.java
@@ -13,6 +13,8 @@
  *******************************************************************************/
 package org.eclipse.pde.ui.tests.launcher;
 
+import static java.util.Map.ofEntries;
+import static org.eclipse.pde.ui.tests.util.TargetPlatformUtil.bundle;
 import static org.junit.Assert.assertEquals;
 
 import java.nio.file.Path;
@@ -45,11 +47,11 @@
 
 	@Test
 	public void testGetMergedBundleMap_startDataParsing() throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0"), //
 				bundle("plugin.b", "1.0.0"), //
 				bundle("plugin.c", "1.0.0"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.f", "1.0.0"));
 
 		Consumer<ILaunchConfigurationWorkingCopy> launchConfigSetup = wc -> {
@@ -69,12 +71,12 @@
 
 	@Test
 	public void testGetMergedBundleMap_mixedPluginsFromWorkspaceAndTarget_specificTargetVersion() throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0"), //
 				bundle("plugin.a", "2.0.0"), //
 				bundle("plugin.b", "1.0.0"), //
 				bundle("plugin.c", "1.0.0"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.x", "1.0.0"), //
 				bundle("plugin.x", "2.0.0"), //
 				bundle("plugin.x", "3.0.0"), //
@@ -100,14 +102,14 @@
 	@Test
 	public void testGetMergedBundleMap_mixedPluginsFromWorkspaceWithAutomaticAddAndTarget_specificTargetVersion()
 			throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0"), //
 				bundle("plugin.a", "2.0.0"), //
 				bundle("plugin.b", "1.0.0"), //
 				bundle("plugin.c", "1.0.0"), //
 				bundle("plugin.d", "1.0.0"), //
 				bundle("plugin.d", "2.0.0"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.x", "1.0.0"), //
 				bundle("plugin.x", "2.0.0"), //
 				bundle("plugin.x", "3.0.0"), //
@@ -138,9 +140,9 @@
 
 	@Test
 	public void testGetMergedBundleMap_singleWorkspacePluginVersion_specificVersion() throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.b", "1.0.0"));
 
 		Consumer<ILaunchConfigurationWorkingCopy> launchConfigSetup = wc -> {
@@ -154,9 +156,9 @@
 
 	@Test
 	public void testGetMergedBundleMap_singleWorkspacePluginVersion_noVersion() throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.b", "1.0.0"));
 
 		Consumer<ILaunchConfigurationWorkingCopy> launchConfigSetup = wc -> {
@@ -170,9 +172,9 @@
 
 	@Test
 	public void testGetMergedBundleMap_singleWorkspacePluginVersion_notMatchingVersion() throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.1"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.b", "1.0.0"));
 
 		Consumer<ILaunchConfigurationWorkingCopy> launchConfigSetup = wc -> {
@@ -186,10 +188,10 @@
 
 	@Test
 	public void testGetMergedBundleMap_multipleWorkspacePluginVersions_specificVersion() throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0"), //
 				bundle("plugin.a", "2.0.0"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.b", "1.0.0"));
 
 		Consumer<ILaunchConfigurationWorkingCopy> launchConfigSetup = wc -> {
@@ -203,11 +205,11 @@
 
 	@Test
 	public void testGetMergedBundleMap_multipleWorkspacePluginVersions_multipleSpecificVersion() throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0"), //
 				bundle("plugin.a", "1.0.1"), //
 				bundle("plugin.a", "2.0.0"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.b", "1.0.0"));
 
 		Consumer<ILaunchConfigurationWorkingCopy> launchConfigSetup = wc -> {
@@ -224,10 +226,10 @@
 
 	@Test
 	public void testGetMergedBundleMap_multipleWorkspacePluginVersions_noVersion() throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0"), //
 				bundle("plugin.a", "2.0.0"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.b", "1.0.0"));
 
 		Consumer<ILaunchConfigurationWorkingCopy> launchConfigSetup = wc -> {
@@ -243,9 +245,9 @@
 	@Test
 	public void testGetMergedBundleMap_multipleWorkspacePluginVersions_sameVersion() throws Exception {
 		ProjectUtils.createPluginProject("another.project", "plugin.a", "1.0.0");
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.b", "1.0.0"));
 
 		Consumer<ILaunchConfigurationWorkingCopy> launchConfigSetup = wc -> {
@@ -260,10 +262,10 @@
 	@Test
 	public void testGetMergedBundleMap_multipleWorkspacePluginVersions_sameMMMVersionButDifferentQualifier()
 			throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0.qualifier"), //
 				bundle("plugin.a", "1.0.0.202111250056"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.b", "1.0.0"));
 
 		Consumer<ILaunchConfigurationWorkingCopy> launchConfigSetup = wc -> {
@@ -282,11 +284,11 @@
 
 	@Test
 	public void testGetMergedBundleMap_automaticAddedWorkspacePlugins_noDisabledPlugins() throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0"), //
 				bundle("plugin.a", "2.0.0"), //
 				bundle("plugin.c", "3.0.0"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.b", "1.0.0"));
 
 		Consumer<ILaunchConfigurationWorkingCopy> launchConfigSetup = wc -> {
@@ -305,11 +307,11 @@
 	@Test
 	public void testGetMergedBundleMap_automaticAddedWorkspacePlugins_singleVersionPluginDisabledWithoutVersion()
 			throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0"), //
 				bundle("plugin.c", "3.0.0"), //
 				bundle("plugin.d", "3.0.0"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.b", "1.0.0"));
 
 		Consumer<ILaunchConfigurationWorkingCopy> launchConfigSetup = wc -> {
@@ -328,11 +330,11 @@
 	@Test
 	public void testGetMergedBundleMap_automaticAddedWorkspacePlugins_singleVersionPluginV1_0_0DeselectedButHaveV1_0_1InWorkspace()
 			throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0"), //
 				bundle("plugin.c", "1.0.1"), //
 				bundle("plugin.d", "1.0.0"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.b", "1.0.0"));
 
 		Consumer<ILaunchConfigurationWorkingCopy> launchConfigSetup = wc -> {
@@ -358,11 +360,11 @@
 	@Test
 	public void testGetMergedBundleMap_automaticAddedWorkspacePlugins_multiVersionPluginDisabledWithSpecificVersion()
 			throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0"), //
 				bundle("plugin.a", "2.0.0"), //
 				bundle("plugin.a", "3.0.0"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.b", "1.0.0"));
 
 		Consumer<ILaunchConfigurationWorkingCopy> launchConfigSetup = wc -> {
@@ -382,11 +384,11 @@
 	@Test
 	public void testGetMergedBundleMap_automaticAddedWorkspacePlugins_multiVersionPluginDisabledWithMultipleSpecificVersions()
 			throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0"), //
 				bundle("plugin.a", "2.0.0"), //
 				bundle("plugin.a", "3.0.0"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.b", "1.0.0"));
 
 		Consumer<ILaunchConfigurationWorkingCopy> launchConfigSetup = wc -> {
@@ -405,11 +407,11 @@
 	public void testGetMergedBundleMap_automaticAddedWorkspacePlugins_multiVersionPluginDisabledWithoutVersion()
 			throws Exception {
 
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0"), //
 				bundle("plugin.a", "2.0.0"), //
 				bundle("plugin.a", "3.0.0"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.b", "1.0.0"));
 
 		Consumer<ILaunchConfigurationWorkingCopy> launchConfigSetup = wc -> {
@@ -426,12 +428,12 @@
 	@Test
 	public void testGetMergedBundleMap_automaticAddedWorkspacePlugins_sameMMMVersionButDifferentQualifier()
 			throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0.qualifier"), //
 				bundle("plugin.a", "1.0.0.202111250056"), //
 				bundle("plugin.b", "2.0.0.qualifier"), //
 				bundle("plugin.b", "2.0.0.202111250056"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.b", "1.0.0"));
 
 		Consumer<ILaunchConfigurationWorkingCopy> launchConfigSetup = wc -> {
@@ -452,9 +454,9 @@
 
 	@Test
 	public void testGetMergedBundleMap_singleTargetPluginVersion_specificVersion() throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.b", "1.0.0"));
 
 		Consumer<ILaunchConfigurationWorkingCopy> launchConfigSetup = wc -> {
@@ -468,9 +470,9 @@
 
 	@Test
 	public void testGetMergedBundleMap_singleTargetPluginVersion_noVersion() throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.b", "1.0.0"));
 
 		Consumer<ILaunchConfigurationWorkingCopy> launchConfigSetup = wc -> {
@@ -484,9 +486,9 @@
 
 	@Test
 	public void testGetMergedBundleMap_singleTargetPluginVersion_notMatchingVersion() throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.b", "1.0.1"));
 
 		Consumer<ILaunchConfigurationWorkingCopy> launchConfigSetup = wc -> {
@@ -500,9 +502,9 @@
 
 	@Test
 	public void testGetMergedBundleMap_multipleTargetPluginVersions_specificVersion() throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.b", "1.0.0"), //
 				bundle("plugin.b", "2.0.0"));
 
@@ -517,9 +519,9 @@
 
 	@Test
 	public void testGetMergedBundleMap_multipleTargetPluginVersions_multipleSpecificVersion() throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.b", "1.0.0"), //
 				bundle("plugin.b", "2.0.0"), //
 				bundle("plugin.b", "3.0.0"));
@@ -537,9 +539,9 @@
 
 	@Test
 	public void testGetMergedBundleMap_multipleTargetPluginVersions_noVersion() throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.b", "1.0.0"), //
 				bundle("plugin.b", "2.0.0"));
 
@@ -554,10 +556,10 @@
 
 	@Test
 	public void testGetMergedBundleMap_singleTargetPluginVersion_notSelectedWorkspacePendant() throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0"), //
 				bundle("plugin.b", "1.0.0"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.a", "1.0.0"), //
 				bundle("plugin.b", "1.0.0"));
 
@@ -575,9 +577,9 @@
 	@Test
 	public void testGetMergedBundleMap_multipleTargetPluginVersions_sameMMMVersionButDifferentQualifier()
 			throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.1.qualifier"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.a", "1.0.0.2020"), //
 				bundle("plugin.a", "1.0.0.2021"));
 
@@ -596,10 +598,10 @@
 
 	@Test
 	public void testGetMergedBundleMap_pluginFromWorkspaceAndTarget_specificTargetVersion() throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0"), //
 				bundle("plugin.b", "1.0.0"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.a", "1.0.1"), //
 				bundle("plugin.b", "2.0.0"));
 
@@ -620,10 +622,10 @@
 
 	@Test
 	public void testGetMergedBundleMap_pluginFromWorkspaceAndTarget_noTargetVersion() throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0"), //
 				bundle("plugin.b", "2.0.0"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.a", "1.0.1"), //
 				bundle("plugin.b", "3.0.0"));
 
@@ -644,9 +646,9 @@
 
 	@Test
 	public void testGetMergedBundleMap_pluginFromWorkspaceAndTarget_notMatchingTargetVersion() throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.a", "1.0.2"));
 
 		Consumer<ILaunchConfigurationWorkingCopy> launchConfigSetup = wc -> {
@@ -664,10 +666,10 @@
 	@Test
 	public void testGetMergedBundleMap_pluginFromWorkspaceAndTarget_targetBundleReplacedByWorkspaceBundleWithSameVersion()
 			throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0"), //
 				bundle("plugin.b", "1.0.0.qualifier"));
-		List<NameVersionDescriptor> targetBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.a", "1.0.0"), //
 				bundle("plugin.b", "1.0.0.202111102345"));
 
@@ -683,16 +685,16 @@
 				workspaceBundle("plugin.a", "1.0.0"), //
 				workspaceBundle("plugin.b", "1.0.0.qualifier"));
 
-		assertGetMergedBundleMap(workspacePlugins, targetBundles, launchConfig, expectedBundles);
+		assertGetMergedBundleMap(workspacePlugins, targetPlatformBundles, launchConfig, expectedBundles);
 	}
 
 	@Test
 	public void testGetMergedBundleMap_workspacePluginAddedAutomaticallyAndTargetPlugin_differentVersions()
 			throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0"), //
 				bundle("plugin.b", "1.0.0"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.a", "1.0.1"), //
 				bundle("plugin.b", "1.0.1"));
 
@@ -713,9 +715,9 @@
 	@Test
 	public void testGetMergedBundleMap_workspacePluginAddedAutomaticallyAndTargetPlugin_sameVersionLikeInWorkspace()
 			throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.a", "1.0.0.202111102345"));
 
 		Consumer<ILaunchConfigurationWorkingCopy> launchConfigSetup = wc -> {
@@ -733,9 +735,9 @@
 
 	@Test
 	public void testWriteBundleEntry_singleWorkspacePlugin_noVersionEntry() throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.a", "2.0.0"), //
 				bundle("plugin.a", "2.0.1"));
 		setUpWorkspace(workspacePlugins, targetPlatformBundles);
@@ -748,10 +750,10 @@
 
 	@Test
 	public void testWriteBundleEntry_oneOfTwoWorkspacePlugins_versionEntry() throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0"), //
 				bundle("plugin.a", "1.0.1"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.a", "2.0.0"));
 		setUpWorkspace(workspacePlugins, targetPlatformBundles);
 
@@ -763,10 +765,10 @@
 
 	@Test
 	public void testWriteBundleEntry_singleTargetPlugin_noVersionEntry() throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0"), //
 				bundle("plugin.a", "1.0.1"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.a", "2.0.0"));
 		setUpWorkspace(workspacePlugins, targetPlatformBundles);
 
@@ -778,9 +780,9 @@
 
 	@Test
 	public void testWriteBundleEntry_oneOfTwoTargetPlugins_versionEntry() throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0"));
-		List<NameVersionDescriptor> targetPlatformBundles = List.of( //
+		var targetPlatformBundles = ofEntries( //
 				bundle("plugin.a", "2.0.0"), //
 				bundle("plugin.a", "2.0.1"));
 		setUpWorkspace(workspacePlugins, targetPlatformBundles);
@@ -793,10 +795,10 @@
 
 	@Test
 	public void testWriteBundleEntry_startLevelAndAutoStart() throws Exception {
-		List<NameVersionDescriptor> workspacePlugins = List.of( //
+		var workspacePlugins = ofEntries( //
 				bundle("plugin.a", "1.0.0"), //
 				bundle("plugin.a", "2.0.0"));
-		setUpWorkspace(workspacePlugins, List.of());
+		setUpWorkspace(workspacePlugins, Map.of());
 
 		IPluginModelBase plugin = workspaceBundle("plugin.a", "1.0.0").findModel();
 
@@ -809,8 +811,9 @@
 
 	// --- utilities ---
 
-	private void assertGetMergedBundleMap(List<NameVersionDescriptor> workspacePlugins,
-			List<NameVersionDescriptor> targetPlugins, Consumer<ILaunchConfigurationWorkingCopy> launchConfigPreparer,
+	private void assertGetMergedBundleMap(Map<NameVersionDescriptor, Map<String, String>> workspacePlugins,
+			Map<NameVersionDescriptor, Map<String, String>> targetPlugins,
+			Consumer<ILaunchConfigurationWorkingCopy> launchConfigPreparer,
 			Set<BundleLocationDescriptor> expectedBundles) throws Exception {
 
 		Map<BundleLocationDescriptor, String> expectedBundleMap = expectedBundles.stream()
@@ -818,8 +821,9 @@
 		assertGetMergedBundleMap(workspacePlugins, targetPlugins, launchConfigPreparer, expectedBundleMap);
 	}
 
-	private void assertGetMergedBundleMap(List<NameVersionDescriptor> workspacePlugins,
-			List<NameVersionDescriptor> targetPlugins, Consumer<ILaunchConfigurationWorkingCopy> launchConfigPreparer,
+	private void assertGetMergedBundleMap(Map<NameVersionDescriptor, Map<String, String>> workspacePlugins,
+			Map<NameVersionDescriptor, Map<String, String>> targetPlugins,
+			Consumer<ILaunchConfigurationWorkingCopy> launchConfigPreparer,
 			Map<BundleLocationDescriptor, String> expectedBundleMap) throws Exception {
 
 		setUpWorkspace(workspacePlugins, targetPlugins);
@@ -837,10 +841,10 @@
 		assertPluginMapsEquals(null, expectedPluginMap, bundleMap);
 	}
 
-	private void setUpWorkspace(List<NameVersionDescriptor> workspacePlugins, List<NameVersionDescriptor> targetPlugins)
-			throws Exception {
-		ProjectUtils.createWorkspacePluginProjects(workspacePlugins);
-		TargetPlatformUtil.setDummyBundlesAsTarget(targetPlugins, tpJarDirectory);
+	private void setUpWorkspace(Map<NameVersionDescriptor, Map<String, String>> workspacePlugins,
+			Map<NameVersionDescriptor, Map<String, String>> targetPlugins) throws Exception {
+		ProjectUtils.createWorkspacePluginProjects(workspacePlugins.keySet());
+		TargetPlatformUtil.setDummyBundlesAsTarget(targetPlugins, List.of(), tpJarDirectory);
 	}
 
 	private static ILaunchConfigurationWorkingCopy createPluginLaunchConfig(String name) throws CoreException {
diff --git a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/util/ProjectUtils.java b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/util/ProjectUtils.java
index 51d51e8..4e2ef4f 100644
--- a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/util/ProjectUtils.java
+++ b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/util/ProjectUtils.java
@@ -157,7 +157,7 @@
 		return project;
 	}
 
-	public static List<IProject> createWorkspacePluginProjects(List<NameVersionDescriptor> workspacePlugins)
+	public static List<IProject> createWorkspacePluginProjects(Iterable<NameVersionDescriptor> workspacePlugins)
 			throws CoreException {
 		List<IProject> projects = new ArrayList<>();
 		for (NameVersionDescriptor plugin : workspacePlugins) {
diff --git a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/util/TargetPlatformUtil.java b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/util/TargetPlatformUtil.java
index 7173c3b..038e8b8 100644
--- a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/util/TargetPlatformUtil.java
+++ b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/util/TargetPlatformUtil.java
@@ -12,14 +12,18 @@
  *     Julian Honnen <julian.honnen@vector.com> - initial API and implementation
  *     Hannes Wellmann - Bug 577116: Improve test utility method reusability
  *     Hannes Wellmann - Bug 577385: Add tests for Plug-in based Eclipse-App launches
+ *     Hannes Wellmann - Bug 544838 - Unify and generalize test bundle/feature creation
  *******************************************************************************/
 package org.eclipse.pde.ui.tests.util;
 
+import static java.util.Map.entry;
+
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.*;
+import java.util.Map.Entry;
 import java.util.function.Predicate;
 import java.util.jar.*;
 import java.util.stream.Collectors;
@@ -57,23 +61,19 @@
 			return;
 		}
 		List<ITargetLocation> bundleContainers = new ArrayList<>();
-		List<NameVersionDescriptor> included = new ArrayList<>();
-		addRunningPlatformBundles(bundleContainers, included, bundleFilter);
+		List<NameVersionDescriptor> included = addRunningPlatformBundles(bundleContainers, bundleFilter);
 		createAndSetTargetForWorkspace(name, bundleContainers, included);
 	}
 
-	public static void setRunningPlatformWithDummyBundlesAsTarget(List<NameVersionDescriptor> targetPlugins,
-			Path jarDirectory, Predicate<Bundle> bundleFilter) throws IOException, InterruptedException {
+	public static void setRunningPlatformWithDummyBundlesAsTarget(Predicate<Bundle> rpBundleFilter,
+			Map<NameVersionDescriptor, Map<String, String>> bundles, Collection<NameVersionDescriptor> features,
+			Path jarDirectory) throws Exception {
+
 		Set<ITargetLocation> locations = new LinkedHashSet<>();
-		Set<NameVersionDescriptor> included = new LinkedHashSet<>();
+		List<NameVersionDescriptor> rpBundles = addRunningPlatformBundles(locations, rpBundleFilter);
+		locations.add(createDummyBundlesLocation(bundles, jarDirectory));
 
-		addRunningPlatformBundles(locations, included, bundleFilter);
-
-		ITargetLocation location = createDummyBundlesLocation(targetPlugins, Map.of(), jarDirectory);
-		locations.add(location);
-		included.addAll(targetPlugins);
-
-		createAndSetTargetForWorkspace(null, locations, included);
+		createAndSetTargetForWorkspace(null, locations, concat(rpBundles, bundles.keySet(), features));
 	}
 
 	public static void loadAndSetTargetForWorkspace(ITargetDefinition target) throws InterruptedException {
@@ -87,8 +87,8 @@
 		}
 	}
 
-	private static void addRunningPlatformBundles(Collection<ITargetLocation> bundleContainers,
-			Collection<NameVersionDescriptor> included, Predicate<Bundle> bundleFilter) {
+	private static List<NameVersionDescriptor> addRunningPlatformBundles(Collection<ITargetLocation> bundleContainers,
+			Predicate<Bundle> bundleFilter) {
 		Bundle[] installedBundles = FrameworkUtil.getBundle(TargetPlatformUtil.class).getBundleContext().getBundles();
 		List<Bundle> targetBundles = Arrays.asList(installedBundles);
 		if (bundleFilter != null) {
@@ -99,9 +99,9 @@
 				.map(File::getParentFile).distinct();
 		containerDirs.map(dir -> TPS.newDirectoryLocation(dir.getAbsolutePath())).forEach(bundleContainers::add);
 
-		for (Bundle bundle : targetBundles) {
-			included.add(new NameVersionDescriptor(bundle.getSymbolicName(), bundle.getVersion().toString()));
-		}
+		return targetBundles.stream()
+				.map(b -> new NameVersionDescriptor(b.getSymbolicName(), b.getVersion().toString()))
+				.collect(Collectors.toList());
 	}
 
 	public static void createAndSetTargetForWorkspace(String name, Collection<ITargetLocation> locations,
@@ -123,27 +123,21 @@
 		loadAndSetTargetForWorkspace(targetDefinition);
 	}
 
-	public static void setDummyBundlesAsTarget(List<NameVersionDescriptor> targetPlugins, Path jarDirectory)
-			throws IOException, InterruptedException {
-		ITargetLocation location = createDummyBundlesLocation(targetPlugins, Map.of(), jarDirectory);
-		createAndSetTargetForWorkspace(null, List.of(location), targetPlugins);
+	public static void setDummyBundlesAsTarget(Map<NameVersionDescriptor, Map<String, String>> bundles,
+			Collection<NameVersionDescriptor> features, Path jarDirectory) throws Exception {
+		ITargetLocation location = createDummyBundlesLocation(bundles, jarDirectory);
+		createAndSetTargetForWorkspace(null, List.of(location), concat(bundles.keySet(), features));
 	}
 
-	public static void setDummyBundlesAsTarget(Map<NameVersionDescriptor, Map<String, String>> pluginDescriptions,
-			Path jarDirectory) throws IOException, InterruptedException {
-		Set<NameVersionDescriptor> pluginIds = pluginDescriptions.keySet();
-		ITargetLocation location = createDummyBundlesLocation(pluginIds, pluginDescriptions, jarDirectory);
-		createAndSetTargetForWorkspace(null, List.of(location), pluginIds);
-	}
-
-	private static ITargetLocation createDummyBundlesLocation(Collection<NameVersionDescriptor> targetPlugins,
-			Map<NameVersionDescriptor, Map<String, String>> pluginAttributes, Path jarDirectory) throws IOException {
+	private static ITargetLocation createDummyBundlesLocation(Map<NameVersionDescriptor, Map<String, String>> plugins,
+			Path jarDirectory) throws IOException {
 		Path pluginsDirectory = jarDirectory.resolve("plugins");
 		Files.createDirectories(pluginsDirectory);
-		for (NameVersionDescriptor bundleNameVersion : targetPlugins) {
+		for (Entry<NameVersionDescriptor, Map<String, String>> entry : plugins.entrySet()) {
+			NameVersionDescriptor bundleNameVersion = entry.getKey();
+			Map<String, String> extraAttributes = entry.getValue();
 
 			Manifest manifest = createDummyBundleManifest(bundleNameVersion.getId(), bundleNameVersion.getVersion());
-			Map<String, String> extraAttributes = pluginAttributes.get(bundleNameVersion);
 			if (extraAttributes != null) {
 				extraAttributes.forEach(manifest.getMainAttributes()::putValue);
 			}
@@ -173,4 +167,26 @@
 		return manifest;
 	}
 
+	@SafeVarargs
+	public static Entry<NameVersionDescriptor, Map<String, String>> bundle(String id, String version,
+			Entry<String, String>... additionalManifestEntries) {
+		return entry(new NameVersionDescriptor(id, version), Map.ofEntries(additionalManifestEntries));
+	}
+
+	public static String version(String version) {
+		return ";" + Constants.VERSION_ATTRIBUTE + "=\"" + version + "\"";
+	}
+
+	public static String bundleVersion(String lowerBound, String upperBound) {
+		return ";" + Constants.BUNDLE_VERSION_ATTRIBUTE + "=\"[" + lowerBound + "," + upperBound + ")\"";
+	}
+
+	public static String resolution(String resolutionType) {
+		return ";" + Constants.RESOLUTION_DIRECTIVE + ":=\"" + resolutionType + "\"";
+	}
+
+	@SafeVarargs
+	private static <T> Set<T> concat(Collection<T>... colls) {
+		return Arrays.stream(colls).flatMap(Collection::stream).collect(Collectors.toCollection(LinkedHashSet::new));
+	}
 }