Bug 351396 - Ignore test-fragments when adding requirements to launch

And unify detection of test-fragments.

Change-Id: Ia0a81f1f54c8f5de01cc33e2d3a068fd9f40420d
Signed-off-by: Hannes Wellmann <wellmann.hannes1@gmx.net>
Reviewed-on: https://git.eclipse.org/r/c/pde/eclipse.pde.ui/+/189927
Tested-by: PDE Bot <pde-bot@eclipse.org>
Reviewed-by: Julian Honnen <julian.honnen@vector.com>
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ClasspathComputer.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ClasspathComputer.java
index fe67cbf..db606bd 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ClasspathComputer.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ClasspathComputer.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2005, 2020 IBM Corporation and others.
+ * Copyright (c) 2005, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -21,6 +21,7 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.regex.Pattern;
+import java.util.stream.Stream;
 import org.eclipse.core.resources.IFile;
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IResource;
@@ -87,9 +88,7 @@
 	}
 
 	private static void addSourceAndLibraries(IProject project, IPluginModelBase model, IBuild build, boolean clear, Map<?, ?> sourceLibraryMap, ArrayList<IClasspathEntry> result) throws CoreException {
-		String testPluginPattern = PDECore.getDefault().getPreferencesManager().getString(ICoreConstants.TEST_PLUGIN_PATTERN);
-		boolean isTestPlugin = testPluginPattern != null && testPluginPattern.length() > 0
-				&& Pattern.compile(testPluginPattern).matcher(project.getName()).find();
+		boolean isTestPlugin = hasTestPluginName(project);
 		HashSet<IPath> paths = new HashSet<>();
 
 		// keep existing source folders
@@ -141,21 +140,52 @@
 		}
 	}
 
-	private static IClasspathEntry updateTestAttribute(boolean isTestPlugin, IClasspathEntry entry) {
+	public static boolean hasTestPluginName(IProject project) {
+		String pattern = PDECore.getDefault().getPreferencesManager().getString(ICoreConstants.TEST_PLUGIN_PATTERN);
+		return pattern != null && !pattern.isEmpty() && Pattern.compile(pattern).matcher(project.getName()).find();
+	}
+
+	/**
+	 * Returns true if the given project is a java project that has
+	 * {@code IClasspathEntry#CPE_SOURCE source classpath-entries} that are all
+	 * marked as {@code IClasspathAttribute#TEST test sources}.
+	 *
+	 * @param project
+	 * @return true if the given project is a test java project
+	 */
+	public static boolean hasTestOnlyClasspath(IProject project) {
+		IJavaProject javaProject = JavaCore.create(project);
+		if (javaProject == null) {
+			return false;
+		}
+		try {
+			boolean hasSources = false;
+			for (IClasspathEntry entry : javaProject.getRawClasspath()) {
+				if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
+					hasSources = true;
+					if (!entry.isTest()) {
+						return false;
+					}
+				}
+			}
+			return hasSources; // if it has sources, all are test-sources
+		} catch (JavaModelException e) { // assume no valid java-project
+		}
+		return false;
+	}
+
+	public static IClasspathEntry updateTestAttribute(boolean isTestPlugin, IClasspathEntry entry) {
 		if (isTestPlugin == entry.isTest() || entry.getEntryKind() != IClasspathEntry.CPE_SOURCE) {
 			return entry;
 		}
-		IClasspathAttribute[] classpathAttributes = Arrays.stream(entry.getExtraAttributes())
-				.filter(e -> !e.getName().equals(IClasspathAttribute.TEST)).toArray(IClasspathAttribute[]::new);
+		Stream<IClasspathAttribute> cpAttributes = Arrays.stream(entry.getExtraAttributes())
+				.filter(e -> !e.getName().equals(IClasspathAttribute.TEST));
 		if (isTestPlugin) {
-			int length = classpathAttributes.length;
-			System.arraycopy(classpathAttributes, 0, classpathAttributes = new IClasspathAttribute[length + 1], 0,
-					length);
-			classpathAttributes[length] = JavaCore.newClasspathAttribute(IClasspathAttribute.TEST, "true"); //$NON-NLS-1$
+			IClasspathAttribute testAttribute = JavaCore.newClasspathAttribute(IClasspathAttribute.TEST, "true"); //$NON-NLS-1$
+			cpAttributes = Stream.concat(cpAttributes, Stream.of(testAttribute));
 		}
-		return JavaCore.newSourceEntry(entry.getPath(), entry.getInclusionPatterns(),
-				entry.getExclusionPatterns(), entry.getOutputLocation(), classpathAttributes);
-
+		return JavaCore.newSourceEntry(entry.getPath(), entry.getInclusionPatterns(), entry.getExclusionPatterns(),
+				entry.getOutputLocation(), cpAttributes.toArray(IClasspathAttribute[]::new));
 	}
 
 	private static IClasspathAttribute[] getClasspathAttributes(IProject project, IPluginModelBase model) {
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/DependencyManager.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/DependencyManager.java
index ef8496a..971cd1d 100644
--- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/DependencyManager.java
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/DependencyManager.java
@@ -24,10 +24,12 @@
 import java.util.List;
 import java.util.Queue;
 import java.util.Set;
+import org.eclipse.core.resources.IResource;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.osgi.service.resolver.BundleDescription;
 import org.eclipse.osgi.service.resolver.State;
 import org.eclipse.pde.core.plugin.IPluginModelBase;
+import org.eclipse.pde.core.plugin.PluginRegistry;
 import org.eclipse.pde.core.target.ITargetPlatformService;
 import org.eclipse.pde.core.target.NameVersionDescriptor;
 import org.osgi.framework.Constants;
@@ -53,8 +55,16 @@
 		/** Specifies to include all optional dependencies into the closure. */
 		INCLUDE_OPTIONAL_DEPENDENCIES,
 
-		/** Specifies to include all fragments into the closure. */
+		/**
+		 * Specifies to include all fragments into the closure (must not be
+		 * combined with {@link #INCLUDE_NON_TEST_FRAGMENTS}).
+		 */
 		INCLUDE_ALL_FRAGMENTS,
+		/**
+		 * Specifies to include all non-test fragments into the closure (must
+		 * not be combined with {@link #INCLUDE_ALL_FRAGMENTS}).
+		 */
+		INCLUDE_NON_TEST_FRAGMENTS;
 	}
 
 	/**
@@ -153,6 +163,10 @@
 		Set<Options> optionSet = Set.of(options);
 		boolean includeOptional = optionSet.contains(Options.INCLUDE_OPTIONAL_DEPENDENCIES);
 		boolean includeAllFragments = optionSet.contains(Options.INCLUDE_ALL_FRAGMENTS);
+		boolean includeNonTestFragments = optionSet.contains(Options.INCLUDE_NON_TEST_FRAGMENTS);
+		if (includeAllFragments && includeNonTestFragments) {
+			throw new AssertionError("Cannot combine INCLUDE_ALL_FRAGMENTS and INCLUDE_NON_TEST_FRAGMENTS"); //$NON-NLS-1$
+		}
 
 		Set<BundleDescription> closure = new HashSet<>(bundles.size() * 4 / 3 + 1);
 		Queue<BundleDescription> pending = new ArrayDeque<>();
@@ -180,10 +194,12 @@
 				}
 			}
 
-			if (includeAllFragments) {
+			if (includeAllFragments || includeNonTestFragments) {
 				// A fragment's host is already required by a wire
 				for (BundleDescription fragment : bundle.getFragments()) {
-					addNewRequiredBundle(fragment, closure, pending);
+					if (includeAllFragments || !isTestWorkspaceProject(fragment)) {
+						addNewRequiredBundle(fragment, closure, pending);
+					}
 				}
 			}
 		}
@@ -208,6 +224,18 @@
 		return Constants.RESOLUTION_OPTIONAL.equals(requirement.getDirectives().get(Constants.RESOLUTION_DIRECTIVE));
 	}
 
+	private static boolean isTestWorkspaceProject(BundleDescription f) {
+		// Be defensive when declaring a fragment as 'test'-fragment
+		IPluginModelBase pluginModel = PluginRegistry.findModel(f);
+		if (pluginModel != null) {
+			IResource resource = pluginModel.getUnderlyingResource();
+			if (resource != null) {
+				return ClasspathComputer.hasTestOnlyClasspath(resource.getProject());
+			} // test-fragments are usually not part of the target-platform
+		}
+		return false;
+	}
+
 	/**
 	 * Computes the set of implicit dependencies from the
 	 * {@link PDEPreferencesManager}.
diff --git a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/BundleLauncherHelper.java b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/BundleLauncherHelper.java
index 4c9ad9f..77e0d3a 100644
--- a/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/BundleLauncherHelper.java
+++ b/ui/org.eclipse.pde.launching/src/org/eclipse/pde/internal/launching/launcher/BundleLauncherHelper.java
@@ -153,7 +153,7 @@
 			}
 		}
 		// Get all required plugins
-		Set<BundleDescription> additionalBundles = DependencyManager.getDependencies(launchPlugins, DependencyManager.Options.INCLUDE_ALL_FRAGMENTS);
+		Set<BundleDescription> additionalBundles = DependencyManager.getDependencies(launchPlugins, DependencyManager.Options.INCLUDE_NON_TEST_FRAGMENTS);
 		for (BundleDescription bundle : additionalBundles) {
 			IPluginModelBase plugin = getRequiredPlugin(bundle.getSymbolicName(), bundle.getVersion().toString(), IMatchRules.PERFECT, defaultPluginResolution);
 			launchPlugins.add(Objects.requireNonNull(plugin));// should never be null
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 3cbb5ce..696a3d9 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
@@ -19,6 +19,7 @@
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.eclipse.pde.internal.core.DependencyManager.findRequirementsClosure;
 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.osgi.framework.Constants.EXPORT_PACKAGE;
 import static org.osgi.framework.Constants.FRAGMENT_HOST;
@@ -29,11 +30,18 @@
 
 import java.io.IOException;
 import java.nio.file.Path;
-import java.util.Map;
+import java.util.*;
 import java.util.Map.Entry;
-import java.util.Set;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.JavaCore;
 import org.eclipse.osgi.service.resolver.BundleDescription;
+import org.eclipse.osgi.service.resolver.VersionRange;
+import org.eclipse.pde.core.plugin.IPluginModelBase;
+import org.eclipse.pde.core.plugin.PluginRegistry;
 import org.eclipse.pde.core.target.NameVersionDescriptor;
+import org.eclipse.pde.internal.core.ClasspathComputer;
 import org.eclipse.pde.internal.core.PluginModelManager;
 import org.eclipse.pde.ui.tests.launcher.AbstractLaunchTest;
 import org.eclipse.pde.ui.tests.util.ProjectUtils;
@@ -189,6 +197,41 @@
 	}
 
 	@Test
+	public void testFindRequirementsClosure_includeNonTestFragments() throws Exception {
+
+		setTargetPlatform( //
+				bundle("bundle.a", "1.0.0", //
+						entry(EXPORT_PACKAGE, "bundle.a.pack" + version("1.0.0"))),
+
+				bundle("bundle.fragment", "1.0.0", //
+						entry(FRAGMENT_HOST, "bundle.a")),
+
+				bundle("other.tests", "1.0.0", //
+						entry(FRAGMENT_HOST, "bundle.a")));
+
+		BundleDescription bundleA = bundleDescription("bundle.a", "1.0.0");
+		BundleDescription bundleFragment = bundleDescription("bundle.fragment", "1.0.0");
+		BundleDescription otherFragmentWithTestName = bundleDescription("other.tests", "1.0.0");
+
+		BundleDescription testFragmentWithTestAttr = createFragmentProject("bundle.a1.tests", "bundle.a", true);
+		BundleDescription testFragmentWithOtherName = createFragmentProject("bundle.a.checks", "bundle.a", true);
+		BundleDescription testFragmentWithoutTestAttr = createFragmentProject("bundle.a2.tests", "bundle.a", false);
+
+		Set<BundleDescription> bundles = Set.of(bundleA);
+
+		Set<BundleDescription> noFragmentsClosure = findRequirementsClosure(bundles);
+		assertThat(noFragmentsClosure).isEqualTo(Set.of(bundleA));
+
+		Set<BundleDescription> allFragmentsClosure = findRequirementsClosure(bundles, INCLUDE_ALL_FRAGMENTS);
+		assertThat(allFragmentsClosure).isEqualTo(Set.of(bundleA, bundleFragment, otherFragmentWithTestName,
+				testFragmentWithTestAttr, testFragmentWithOtherName, testFragmentWithoutTestAttr));
+
+		Set<BundleDescription> nonTestFragmentsClosure = findRequirementsClosure(bundles, INCLUDE_NON_TEST_FRAGMENTS);
+		assertThat(nonTestFragmentsClosure)
+				.isEqualTo(Set.of(bundleA, bundleFragment, otherFragmentWithTestName, testFragmentWithoutTestAttr));
+	}
+
+	@Test
 	public void testFindRequirementsClosure_includeOptional() throws Exception {
 
 		setTargetPlatform( //
@@ -248,7 +291,22 @@
 
 	private static final String OPTIONAL = Constants.RESOLUTION_OPTIONAL;
 
-	private BundleDescription bundleDescription(String id, String version) {
+	private static BundleDescription bundleDescription(String id, String version) {
 		return AbstractLaunchTest.findTargetModel(id, version).getBundleDescription();
 	}
+
+	private static BundleDescription createFragmentProject(String projectName, String hostName,
+			boolean setTestAttribute) throws CoreException {
+
+		IProject project = ProjectUtils.createPluginProject(projectName, projectName, "1.0.0", (d, s) -> {
+			d.setHost(s.newHost(hostName, VersionRange.emptyRange));
+		});
+		IPluginModelBase model = PluginRegistry.findModel(project);
+		if (setTestAttribute) { // set test attribute in classpath
+			IClasspathEntry[] classpath = ClasspathComputer.getClasspath(project, model, null, false, true);
+			var cpEntries = Arrays.stream(classpath).map(e -> ClasspathComputer.updateTestAttribute(true, e));
+			JavaCore.create(project).setRawClasspath(cpEntries.toArray(IClasspathEntry[]::new), null);
+		}
+		return model.getBundleDescription();
+	}
 }
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 05b2b26..51d51e8 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
@@ -18,12 +18,14 @@
 import java.net.URL;
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiConsumer;
 import org.eclipse.core.resources.*;
 import org.eclipse.core.runtime.*;
 import org.eclipse.jdt.core.IJavaProject;
 import org.eclipse.jdt.core.JavaCore;
 import org.eclipse.jdt.launching.environments.IExecutionEnvironment;
 import org.eclipse.pde.core.project.IBundleProjectDescription;
+import org.eclipse.pde.core.project.IBundleProjectService;
 import org.eclipse.pde.core.target.NameVersionDescriptor;
 import org.eclipse.pde.internal.ui.wizards.IProjectProvider;
 import org.eclipse.pde.internal.ui.wizards.plugin.NewProjectCreationOperation;
@@ -165,17 +167,26 @@
 	}
 
 	public static IProject createPluginProject(String bundleSymbolicName, String bundleVersion) throws CoreException {
-		return createPluginProject(bundleSymbolicName + bundleVersion.replace('.', '_'), bundleSymbolicName, bundleVersion);
+		return createPluginProject(bundleSymbolicName + bundleVersion.replace('.', '_'), bundleSymbolicName,
+				bundleVersion);
 	}
 
 	public static IProject createPluginProject(String projectName, String bundleSymbolicName, String version)
 			throws CoreException {
+		return createPluginProject(projectName, bundleSymbolicName, version, (d, s) -> {
+		});
+	}
+
+	public static IProject createPluginProject(String projectName, String bundleSymbolicName, String version,
+			BiConsumer<IBundleProjectDescription, IBundleProjectService> setup) throws CoreException {
 		IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
-		IBundleProjectDescription description = ProjectCreationTests.getBundleProjectService().getDescription(project);
+		IBundleProjectService bundleProjectService = ProjectCreationTests.getBundleProjectService();
+		IBundleProjectDescription description = bundleProjectService.getDescription(project);
 		description.setSymbolicName(bundleSymbolicName);
 		if (version != null) {
 			description.setBundleVersion(Version.parseVersion(version));
 		}
+		setup.accept(description, bundleProjectService);
 		description.apply(null);
 		return project;
 	}
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/product/PluginSection.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/product/PluginSection.java
index 7b4af13..8acbcca 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/product/PluginSection.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/editor/product/PluginSection.java
@@ -356,8 +356,8 @@
 		}).filter(Objects::nonNull).map(IPluginModelBase::getBundleDescription).collect(Collectors.toList());
 
 		DependencyManager.Options[] options = includeOptional
-				? new Options[] { Options.INCLUDE_ALL_FRAGMENTS, Options.INCLUDE_OPTIONAL_DEPENDENCIES }
-				: new Options[] { Options.INCLUDE_ALL_FRAGMENTS };
+				? new Options[] { Options.INCLUDE_NON_TEST_FRAGMENTS, Options.INCLUDE_OPTIONAL_DEPENDENCIES }
+				: new Options[] { Options.INCLUDE_NON_TEST_FRAGMENTS };
 		Set<BundleDescription> dependencies = DependencyManager.findRequirementsClosure(list, options);
 
 		IProduct product = plugins[0].getProduct();
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/AbstractPluginBlock.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/AbstractPluginBlock.java
index 810ace2..4c3c1b8 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/AbstractPluginBlock.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/launcher/AbstractPluginBlock.java
@@ -800,8 +800,8 @@
 				.map(IPluginModelBase.class::cast).collect(Collectors.toList());
 
 		DependencyManager.Options[] options = fIncludeOptionalButton.getSelection()
-				? new Options[] { Options.INCLUDE_ALL_FRAGMENTS, Options.INCLUDE_OPTIONAL_DEPENDENCIES }
-				: new Options[] { Options.INCLUDE_ALL_FRAGMENTS };
+				? new Options[] { Options.INCLUDE_NON_TEST_FRAGMENTS, Options.INCLUDE_OPTIONAL_DEPENDENCIES }
+				: new Options[] { Options.INCLUDE_NON_TEST_FRAGMENTS };
 		Set<BundleDescription> additionalBundles = DependencyManager.getDependencies(toCheck, options);
 
 		additionalBundles.stream().map(PluginRegistry::findModel).filter(Objects::nonNull).forEach(toCheck::add);
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/plugin/NewProjectCreationOperation.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/plugin/NewProjectCreationOperation.java
index 3c239984..27e0fe6 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/plugin/NewProjectCreationOperation.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/plugin/NewProjectCreationOperation.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2020 IBM Corporation and others.
+ * Copyright (c) 2000, 2022 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -20,7 +20,6 @@
 
 import java.lang.reflect.InvocationTargetException;
 import java.util.*;
-import java.util.regex.Pattern;
 import org.eclipse.core.resources.*;
 import org.eclipse.core.runtime.*;
 import org.eclipse.core.runtime.preferences.IEclipsePreferences;
@@ -485,10 +484,7 @@
 		}
 		IClasspathEntry[] entries = new IClasspathEntry[1];
 		IPath path = project.getProject().getFullPath().append(data.getSourceFolderName());
-		String testPluginPattern = PDECore.getDefault().getPreferencesManager()
-				.getString(ICoreConstants.TEST_PLUGIN_PATTERN);
-		boolean isTestPlugin = testPluginPattern != null && testPluginPattern.length() > 0
-				&& Pattern.compile(testPluginPattern).matcher(project.getProject().getName()).find();
+		boolean isTestPlugin = ClasspathComputer.hasTestPluginName(project.getProject());
 		if (isTestPlugin) {
 			IClasspathAttribute testAttribute = JavaCore.newClasspathAttribute(IClasspathAttribute.TEST, "true"); //$NON-NLS-1$
 			entries[0] = JavaCore.newSourceEntry(path, null, null, null, new IClasspathAttribute[] { testAttribute });
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/product/ProductFromExtensionOperation.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/product/ProductFromExtensionOperation.java
index f5e2af4..041df04 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/product/ProductFromExtensionOperation.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/wizards/product/ProductFromExtensionOperation.java
@@ -68,7 +68,7 @@
 			}
 		}
 		Set<BundleDescription> bundles = DependencyManager.findRequirementsClosure(plugins,
-				DependencyManager.Options.INCLUDE_ALL_FRAGMENTS);
+				DependencyManager.Options.INCLUDE_NON_TEST_FRAGMENTS);
 		return bundles.stream().map(BundleDescription::getSymbolicName).toArray(String[]::new);
 	}