diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/RequiredPluginsClasspathContainer.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/RequiredPluginsClasspathContainer.java index 279f6c46e5..7c271e09eb 100644 --- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/RequiredPluginsClasspathContainer.java +++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/RequiredPluginsClasspathContainer.java @@ -85,7 +85,7 @@ class RequiredPluginsClasspathContainer { private static final String JUNIT4_PLUGIN = "org.junit"; private static final VersionRange JUNIT_4_VERSION = new VersionRange("[4.0,5)"); //$NON-NLS-1$ @SuppressWarnings("nls") - private static final Set JUNIT5_RUNTIME_PLUGINS = Set.of("org.junit", // + private static final List JUNIT5_RUNTIME_PLUGINS = List.of("org.junit", // "junit-platform-launcher", "org.junit.platform.launcher", "junit-jupiter-engine", // BSN of the bundle from Maven-Central @@ -223,7 +223,7 @@ private List computePluginEntriesByModel() throws CoreException return List.of(); } - Map> map = retrieveVisiblePackagesFromState(desc); + Map> map = retrieveVisiblePackagesFromState(desc); // Add any library entries contributed via classpath contributor // extension (Bug 363733) @@ -338,8 +338,8 @@ private static synchronized Stream getClasspathContributo return Stream.concat(fClasspathContributors.stream(), PDECore.getDefault().getClasspathContributors()); } - private Map> retrieveVisiblePackagesFromState(BundleDescription desc) { - Map> visiblePackages = new HashMap<>(); + private Map> retrieveVisiblePackagesFromState(BundleDescription desc) { + Map> visiblePackages = new HashMap<>(); StateHelper helper = BundleHelper.getPlatformAdmin().getStateHelper(); addVisiblePackagesFromState(helper, desc, visiblePackages); if (desc.getHost() != null) { @@ -349,7 +349,7 @@ private Map> retrieveVisiblePackagesFromState(Bund } private void addVisiblePackagesFromState(StateHelper helper, BundleDescription desc, - Map> visiblePackages) { + Map> visiblePackages) { if (desc == null) { return; } @@ -359,11 +359,9 @@ private void addVisiblePackagesFromState(StateHelper helper, BundleDescription d if (exporter == null) { continue; } - List list = visiblePackages.computeIfAbsent(exporter, e -> new ArrayList<>()); + LinkedHashSet list = visiblePackages.computeIfAbsent(exporter, e -> new LinkedHashSet<>()); Rule rule = getRule(helper, desc, export); - if (!list.contains(rule)) { - list.add(rule); - } + list.add(rule); } } @@ -375,7 +373,7 @@ private Rule getRule(StateHelper helper, BundleDescription desc, ExportPackageDe } protected void addDependencyViaImportPackage(BundleDescription desc, Set added, - Map> map, List entries) throws CoreException { + Map> map, List entries) throws CoreException { if (desc == null || !added.add(desc)) { return; } @@ -393,12 +391,12 @@ protected void addDependencyViaImportPackage(BundleDescription desc, Set added, - Map> map, List entries) throws CoreException { + Map> map, List entries) throws CoreException { addDependency(desc, added, map, entries, true); } private void addDependency(BundleDescription desc, Set added, - Map> map, List entries, boolean useInclusion) + Map> map, List entries, boolean useInclusion) throws CoreException { if (desc == null || !added.add(desc)) { return; @@ -441,7 +439,7 @@ private void addDependency(BundleDescription desc, Set added, } } - private boolean addPlugin(BundleDescription desc, boolean useInclusions, Map> map, + private boolean addPlugin(BundleDescription desc, boolean useInclusions, Map> map, List entries) throws CoreException { IPluginModelBase model = PluginRegistry.findModel((Resource) desc); if (model == null || !model.isEnabled()) { @@ -469,7 +467,7 @@ private boolean addPlugin(BundleDescription desc, boolean useInclusions, Map getInclusions(Map> map, IPluginModelBase model) { + private List getInclusions(Map> map, IPluginModelBase model) { BundleDescription desc = model.getBundleDescription(); if (desc == null || "false".equals(System.getProperty("pde.restriction")) //$NON-NLS-1$ //$NON-NLS-2$ || !(fModel instanceof IBundlePluginModelBase) || TargetPlatformHelper.getTargetVersion() < 3.1) { @@ -479,12 +477,12 @@ private List getInclusions(Map> map, IPlugin if (desc.getHost() != null) { desc = (BundleDescription) desc.getHost().getSupplier(); } - List rules = map.getOrDefault(desc, List.of()); - return (rules.isEmpty() && !ClasspathUtilCore.hasBundleStructure(model)) ? null : rules; + LinkedHashSet rules = map.getOrDefault(desc, new LinkedHashSet<>()); + return (rules.isEmpty() && !ClasspathUtilCore.hasBundleStructure(model)) ? null : new ArrayList<>(rules); } private void addHostPlugin(HostSpecification hostSpec, Set added, - Map> map, List entries) throws CoreException { + Map> map, List entries) throws CoreException { BaseDescription desc = hostSpec.getSupplier(); if (desc instanceof BundleDescription host) { @@ -628,7 +626,7 @@ private void addJunit5RuntimeDependencies(Set added, List> rules = Map.of(desc, List.of()); + Map> rules = Map.of(desc, new LinkedHashSet<>()); addPlugin(desc, true, rules, entries); } } @@ -691,7 +689,7 @@ private void addTransitiveDependenciesWithForbiddenAccess(Set while (transitiveDeps.hasNext()) { BundleDescription desc = transitiveDeps.next(); if (added.add(desc)) { - Map> rules = Map.of(desc, List.of()); + Map> rules = Map.of(desc, new LinkedHashSet<>()); addPlugin(desc, true, rules, entries); } } @@ -705,21 +703,24 @@ private void addExtraModel(BundleDescription desc, Set added, if (added.contains(bundleDesc)) { return; } - Map> rules = new HashMap<>(); + Map> rules = new HashMap<>(); findExportedPackages(bundleDesc, desc, rules); addDependency(bundleDesc, added, rules, entries, true); } } protected final void findExportedPackages(BundleDescription desc, BundleDescription projectDesc, - Map> map) { + Map> map) { if (desc != null) { Queue queue = new ArrayDeque<>(); queue.add(desc); while (!queue.isEmpty()) { BundleDescription bdesc = queue.remove(); + if (map.containsKey(bdesc)) { + continue; + } ExportPackageDescription[] expkgs = bdesc.getExportPackages(); - List rules = new ArrayList<>(); + LinkedHashSet rules = new LinkedHashSet<>(); for (ExportPackageDescription expkg : expkgs) { boolean discouraged = restrictPackage(projectDesc, expkg); IPath path = IPath.fromOSString(expkg.getName().replace('.', '/') + "/*"); //$NON-NLS-1$ diff --git a/ui/org.eclipse.pde.ui.tests/META-INF/MANIFEST.MF b/ui/org.eclipse.pde.ui.tests/META-INF/MANIFEST.MF index 3a3f32be76..f5ed639eff 100644 --- a/ui/org.eclipse.pde.ui.tests/META-INF/MANIFEST.MF +++ b/ui/org.eclipse.pde.ui.tests/META-INF/MANIFEST.MF @@ -59,6 +59,7 @@ Import-Package: jakarta.annotation;version="[2.1.0,3.0.0)", org.eclipse.pde.internal.build, org.hamcrest, org.junit, + org.junit.jupiter.api;version="[5.8.1,6.0.0)", org.junit.jupiter.api.function;version="[5.8.1,6.0.0)", org.junit.jupiter.migrationsupport;version="[5.13.0,6.0.0)", org.junit.platform.suite.api;version="[1.13.0,2.0.0)", diff --git a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/core/tests/internal/classpath/ChainedReexportPerformanceTest.java b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/core/tests/internal/classpath/ChainedReexportPerformanceTest.java new file mode 100644 index 0000000000..3b1f4c2e5c --- /dev/null +++ b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/core/tests/internal/classpath/ChainedReexportPerformanceTest.java @@ -0,0 +1,189 @@ +/******************************************************************************* + * Copyright (c) 2025 vogella GmbH and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Lars Vogel - initial API and implementation + *******************************************************************************/ +package org.eclipse.pde.core.tests.internal.classpath; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.List; +import java.util.jar.Attributes; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IWorkspaceDescription; +import org.eclipse.core.resources.IncrementalProjectBuilder; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.pde.core.plugin.IPluginModelBase; +import org.eclipse.pde.core.plugin.PluginRegistry; +import org.eclipse.pde.core.project.IBundleProjectDescription; +import org.eclipse.pde.core.project.IBundleProjectService; +import org.eclipse.pde.core.project.IRequiredBundleDescription; +import org.eclipse.pde.core.target.ITargetDefinition; +import org.eclipse.pde.core.target.ITargetLocation; +import org.eclipse.pde.core.target.ITargetPlatformService; +import org.eclipse.pde.internal.core.PDECore; +import org.eclipse.pde.internal.ui.wizards.tools.UpdateClasspathJob; +import org.eclipse.pde.ui.tests.runtime.TestUtils; +import org.eclipse.pde.ui.tests.util.ProjectUtils; +import org.eclipse.pde.ui.tests.util.TargetPlatformUtil; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.osgi.framework.Version; +import org.osgi.framework.VersionRange; + +public class ChainedReexportPerformanceTest { + + private static final String CHAIN_PREFIX = "Chain_"; + private static final int PACKAGE_COUNT = 1000; + private static final int BUNDLE_CHAIN_DEPTH = 5; + private File targetDir; + private ITargetDefinition savedTarget; + + @BeforeAll + public static void beforeAll() throws Exception { + ProjectUtils.deleteAllWorkspaceProjects(); + } + + @AfterAll + public static void afterAll() throws Exception { + ProjectUtils.deleteAllWorkspaceProjects(); + } + + @BeforeEach + public void setUp() throws Exception { + savedTarget = TargetPlatformUtil.TPS.getWorkspaceTargetDefinition(); + + IWorkspaceDescription desc = ResourcesPlugin.getWorkspace().getDescription(); + desc.setAutoBuilding(false); + ResourcesPlugin.getWorkspace().setDescription(desc); + + targetDir = Files.createTempDirectory("pde_chain_perf_target").toFile(); + createChainedTargetPlatform(); + } + + @AfterEach + public void tearDown() throws Exception { + IWorkspaceDescription desc = ResourcesPlugin.getWorkspace().getDescription(); + desc.setAutoBuilding(true); + ResourcesPlugin.getWorkspace().setDescription(desc); + + if (targetDir != null && targetDir.exists()) { + Files.walk(targetDir.toPath()).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); + } + + if (savedTarget != null && savedTarget != TargetPlatformUtil.TPS.getWorkspaceTargetDefinition()) { + TargetPlatformUtil.loadAndSetTarget(savedTarget); + } + } + + private void createChainedTargetPlatform() throws Exception { + // Create a chain of bundles: B_0 -> B_1 -> ... -> B_N (all re-exporting) + for (int i = 0; i < BUNDLE_CHAIN_DEPTH; i++) { + String name = CHAIN_PREFIX + i; + String exports = createPackageExports(name); + String requires = (i > 0) ? (CHAIN_PREFIX + (i - 1) + ";visibility:=reexport") : null; + createBundle(targetDir, name, exports, requires); + } + + ITargetPlatformService tps = PDECore.getDefault().acquireService(ITargetPlatformService.class); + ITargetDefinition target = tps.newTarget(); + target.setTargetLocations(new ITargetLocation[] { tps.newDirectoryLocation(targetDir.getAbsolutePath()) }); + TargetPlatformUtil.loadAndSetTarget(target); + } + + private String createPackageExports(String bundleName) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < PACKAGE_COUNT; i++) { + if (sb.length() > 0) { + sb.append(","); + } + sb.append(bundleName).append(".pkg.").append(i); + } + return sb.toString(); + } + + private void createBundle(File dir, String name, String exports, String requires) throws IOException { + File jarFile = new File(dir, name + ".jar"); + try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(jarFile))) { + Manifest manifest = new Manifest(); + Attributes main = manifest.getMainAttributes(); + main.put(Attributes.Name.MANIFEST_VERSION, "1.0"); + main.put(new Attributes.Name("Bundle-ManifestVersion"), "2"); + main.put(new Attributes.Name("Bundle-SymbolicName"), name); + main.put(new Attributes.Name("Bundle-Version"), "1.0.0"); + if (exports != null) { + main.put(new Attributes.Name("Export-Package"), exports); + } + if (requires != null) { + main.put(new Attributes.Name("Require-Bundle"), requires); + } + + ZipEntry entry = new ZipEntry("META-INF/MANIFEST.MF"); + jos.putNextEntry(entry); + manifest.write(jos); + jos.closeEntry(); + } + } + + @Test + public void testChainedReexportPerformance() throws Exception { + IBundleProjectService service = PDECore.getDefault().acquireService(IBundleProjectService.class); + + String consumerName = "ConsumerBundle"; + IProject consumerProj = ResourcesPlugin.getWorkspace().getRoot().getProject(consumerName); + consumerProj.create(null); + consumerProj.open(null); + + IBundleProjectDescription consumerDesc = service.getDescription(consumerProj); + consumerDesc.setSymbolicName(consumerName); + consumerDesc.setBundleVersion(new Version("1.0.0")); + + // Require the last bundle in the chain with re-export + IRequiredBundleDescription mainReq = service.newRequiredBundle(CHAIN_PREFIX + (BUNDLE_CHAIN_DEPTH - 1), + (VersionRange) null, false, true); + consumerDesc.setRequiredBundles(new IRequiredBundleDescription[] { mainReq }); + + consumerDesc.setNatureIds(new String[] { JavaCore.NATURE_ID, IBundleProjectDescription.PLUGIN_NATURE }); + consumerDesc.apply(null); + + ResourcesPlugin.getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, new NullProgressMonitor()); + TestUtils.waitForJobs("Init", 500, 5000); + + IPluginModelBase consumerModel = PluginRegistry.findModel(consumerProj); + if (consumerModel == null) { + throw new IllegalStateException("Consumer model not found"); + } + + UpdateClasspathJob.scheduleFor(List.of(consumerModel), false); + boolean timedOut = TestUtils.waitForJobs("classpath", 100, 10000); + assertFalse(timedOut, "Performance regression: classpath computation timed out for chained re-exports"); + + IClasspathEntry[] resolvedClasspath = JavaCore.create(consumerProj).getRawClasspath(); + assertTrue(resolvedClasspath.length > 0, "Classpath should not be empty"); + } +} diff --git a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/core/tests/internal/classpath/RequiredPluginsClasspathContainerPerformanceTest.java b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/core/tests/internal/classpath/RequiredPluginsClasspathContainerPerformanceTest.java new file mode 100644 index 0000000000..a35d5ddfcb --- /dev/null +++ b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/core/tests/internal/classpath/RequiredPluginsClasspathContainerPerformanceTest.java @@ -0,0 +1,187 @@ +/******************************************************************************* + * Copyright (c) 2025 vogella GmbH and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Lars Vogel - initial API and implementation + *******************************************************************************/ +package org.eclipse.pde.core.tests.internal.classpath; + +import static org.junit.jupiter.api.Assertions.assertTimeout; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.util.Comparator; +import java.util.List; +import java.util.jar.Attributes; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IWorkspaceDescription; +import org.eclipse.core.resources.IncrementalProjectBuilder; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.pde.core.build.IBuild; +import org.eclipse.pde.core.build.IBuildEntry; +import org.eclipse.pde.core.plugin.IPluginModelBase; +import org.eclipse.pde.core.plugin.PluginRegistry; +import org.eclipse.pde.core.project.IBundleProjectDescription; +import org.eclipse.pde.core.project.IBundleProjectService; +import org.eclipse.pde.core.target.ITargetDefinition; +import org.eclipse.pde.core.target.ITargetLocation; +import org.eclipse.pde.core.target.ITargetPlatformService; +import org.eclipse.pde.internal.core.PDECore; +import org.eclipse.pde.internal.core.build.WorkspaceBuildModel; +import org.eclipse.pde.internal.core.project.PDEProject; +import org.eclipse.pde.internal.ui.wizards.tools.UpdateClasspathJob; +import org.eclipse.pde.ui.tests.runtime.TestUtils; +import org.eclipse.pde.ui.tests.util.ProjectUtils; +import org.eclipse.pde.ui.tests.util.TargetPlatformUtil; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.osgi.framework.Version; + +public class RequiredPluginsClasspathContainerPerformanceTest { + + private static final String CYCLE_BUNDLE_PREFIX = "Cycle_"; + private File targetDir; + private ITargetDefinition savedTarget; + + @BeforeAll + public static void beforeAll() throws Exception { + ProjectUtils.deleteAllWorkspaceProjects(); + } + + @AfterAll + public static void afterAll() throws Exception { + ProjectUtils.deleteAllWorkspaceProjects(); + } + + @BeforeEach + public void setUp() throws Exception { + savedTarget = TargetPlatformUtil.TPS.getWorkspaceTargetDefinition(); + + IWorkspaceDescription desc = ResourcesPlugin.getWorkspace().getDescription(); + desc.setAutoBuilding(false); + ResourcesPlugin.getWorkspace().setDescription(desc); + + targetDir = Files.createTempDirectory("pde_perf_target").toFile(); + createCyclicTargetPlatform(); + } + + @AfterEach + public void tearDown() throws Exception { + IWorkspaceDescription desc = ResourcesPlugin.getWorkspace().getDescription(); + desc.setAutoBuilding(true); + ResourcesPlugin.getWorkspace().setDescription(desc); + + if (targetDir != null && targetDir.exists()) { + Files.walk(targetDir.toPath()).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); + } + + if (savedTarget != null && savedTarget != TargetPlatformUtil.TPS.getWorkspaceTargetDefinition()) { + TargetPlatformUtil.loadAndSetTarget(savedTarget); + } + } + + private void createCyclicTargetPlatform() throws Exception { + // Cycle_A -> reexports Cycle_B + // Cycle_B -> reexports Cycle_C + // Cycle_C -> reexports Cycle_A + createBundle(targetDir, CYCLE_BUNDLE_PREFIX + "A", null, CYCLE_BUNDLE_PREFIX + "B;visibility:=reexport"); + createBundle(targetDir, CYCLE_BUNDLE_PREFIX + "B", null, CYCLE_BUNDLE_PREFIX + "C;visibility:=reexport"); + createBundle(targetDir, CYCLE_BUNDLE_PREFIX + "C", null, CYCLE_BUNDLE_PREFIX + "A;visibility:=reexport"); + + ITargetPlatformService tps = PDECore.getDefault().acquireService(ITargetPlatformService.class); + ITargetDefinition target = tps.newTarget(); + target.setTargetLocations(new ITargetLocation[] { tps.newDirectoryLocation(targetDir.getAbsolutePath()) }); + TargetPlatformUtil.loadAndSetTarget(target); + } + + private void createBundle(File dir, String name, String exports, String requires) throws IOException { + File jarFile = new File(dir, name + ".jar"); + try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(jarFile))) { + Manifest manifest = new Manifest(); + Attributes main = manifest.getMainAttributes(); + main.put(Attributes.Name.MANIFEST_VERSION, "1.0"); + main.put(new Attributes.Name("Bundle-ManifestVersion"), "2"); + main.put(new Attributes.Name("Bundle-SymbolicName"), name); + main.put(new Attributes.Name("Bundle-Version"), "1.0.0"); + if (exports != null) { + main.put(new Attributes.Name("Export-Package"), exports); + } + if (requires != null) { + main.put(new Attributes.Name("Require-Bundle"), requires); + } + + ZipEntry entry = new ZipEntry("META-INF/MANIFEST.MF"); + jos.putNextEntry(entry); + manifest.write(jos); + jos.closeEntry(); + } + } + + @Test + public void testCyclicReexportInSecondaryDependencies() throws Exception { + IBundleProjectService service = PDECore.getDefault().acquireService(IBundleProjectService.class); + + String consumerName = "ConsumerBundle"; + IProject consumerProj = ResourcesPlugin.getWorkspace().getRoot().getProject(consumerName); + consumerProj.create(null); + consumerProj.open(null); + + IBundleProjectDescription consumerDesc = service.getDescription(consumerProj); + consumerDesc.setSymbolicName(consumerName); + consumerDesc.setBundleVersion(new Version("1.0.0")); + consumerDesc.setNatureIds(new String[] { JavaCore.NATURE_ID, IBundleProjectDescription.PLUGIN_NATURE }); + consumerDesc.apply(null); + + // Add secondary dependency to build.properties + IFile buildProps = PDEProject.getBuildProperties(consumerProj); + WorkspaceBuildModel buildModel = new WorkspaceBuildModel(buildProps); + buildModel.load(); + IBuild build = buildModel.getBuild(); + IBuildEntry entry = build.getEntry(IBuildEntry.SECONDARY_DEPENDENCIES); + if (entry == null) { + entry = buildModel.getFactory().createEntry(IBuildEntry.SECONDARY_DEPENDENCIES); + build.add(entry); + } + entry.addToken(CYCLE_BUNDLE_PREFIX + "A"); + buildModel.save(); + + ResourcesPlugin.getWorkspace().build(IncrementalProjectBuilder.FULL_BUILD, new NullProgressMonitor()); + TestUtils.waitForJobs("Init", 500, 5000); + + IPluginModelBase consumerModel = PluginRegistry.findModel(consumerProj); + if (consumerModel == null) { + throw new IllegalStateException("Consumer model not found"); + } + + Job job = UpdateClasspathJob.scheduleFor(List.of(consumerModel), false); + assertTimeout(Duration.ofSeconds(5), () -> job.join(), + "Performance regression or infinite loop: classpath computation timed out for cyclic re-exports"); + + IClasspathEntry[] resolvedClasspath = JavaCore.create(consumerProj).getRawClasspath(); + assertTrue(resolvedClasspath.length > 0, "Classpath should not be empty"); + } +} diff --git a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/AllPDETests.java b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/AllPDETests.java index 8316577644..034af08321 100644 --- a/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/AllPDETests.java +++ b/ui/org.eclipse.pde.ui.tests/src/org/eclipse/pde/ui/tests/AllPDETests.java @@ -14,8 +14,10 @@ package org.eclipse.pde.ui.tests; import org.eclipse.pde.core.tests.internal.AllPDECoreTests; +import org.eclipse.pde.core.tests.internal.classpath.ChainedReexportPerformanceTest; import org.eclipse.pde.core.tests.internal.classpath.ClasspathResolutionTest; import org.eclipse.pde.core.tests.internal.classpath.ClasspathResolutionTest2; +import org.eclipse.pde.core.tests.internal.classpath.RequiredPluginsClasspathContainerPerformanceTest; import org.eclipse.pde.core.tests.internal.core.builders.BundleErrorReporterTest; import org.eclipse.pde.core.tests.internal.util.PDESchemaHelperTest; import org.eclipse.pde.ui.tests.build.properties.AllValidatorTests; @@ -59,6 +61,8 @@ BundleRootTests.class, // PluginRegistryTests.class, // ClasspathResolverTest.class, // + RequiredPluginsClasspathContainerPerformanceTest.class, // + ChainedReexportPerformanceTest.class, // ClasspathUpdaterTest.class, // PDESchemaHelperTest.class, // ClasspathContributorTest.class, //