From a3a2a6e385dd64d2b5dcb46582ceec0e2f3693d4 Mon Sep 17 00:00:00 2001 From: Hannes Wellmann Date: Sat, 23 Sep 2023 00:43:40 +0200 Subject: [PATCH] Simplify/Clean-up code of o.e.pde.junit.runtime --- .../META-INF/MANIFEST.MF | 2 - ui/org.eclipse.pde.junit.runtime/readme.txt | 18 +++ .../junit/runtime/MultiBundleClassLoader.java | 10 +- .../runtime/NonUIThreadTestApplication.java | 81 ++++++++----- .../junit/runtime/PDEJUnitRuntimePlugin.java | 113 ------------------ .../junit/runtime/RemotePluginTestRunner.java | 67 +++++------ .../junit/runtime/UITestApplication.java | 29 +---- 7 files changed, 106 insertions(+), 214 deletions(-) create mode 100644 ui/org.eclipse.pde.junit.runtime/readme.txt delete mode 100644 ui/org.eclipse.pde.junit.runtime/src/org/eclipse/pde/internal/junit/runtime/PDEJUnitRuntimePlugin.java diff --git a/ui/org.eclipse.pde.junit.runtime/META-INF/MANIFEST.MF b/ui/org.eclipse.pde.junit.runtime/META-INF/MANIFEST.MF index c61b9161752..9b0dde7b1ac 100644 --- a/ui/org.eclipse.pde.junit.runtime/META-INF/MANIFEST.MF +++ b/ui/org.eclipse.pde.junit.runtime/META-INF/MANIFEST.MF @@ -6,12 +6,10 @@ Bundle-Version: 3.7.200.qualifier Bundle-Vendor: %providerName Bundle-Localization: plugin Require-Bundle: org.eclipse.jdt.junit.runtime;bundle-version="[3.5.0,4.0.0)", - org.junit;bundle-version="3.8.2", org.eclipse.core.runtime;bundle-version="[3.29.0,4.0.0)", org.eclipse.ui;bundle-version="[3.2.0,4.0.0)";resolution:=optional Export-Package: org.eclipse.pde.internal.junit.runtime;x-internal:=true Bundle-RequiredExecutionEnvironment: JavaSE-17 -Bundle-Activator: org.eclipse.pde.internal.junit.runtime.PDEJUnitRuntimePlugin Bundle-ActivationPolicy: lazy Import-Package: org.eclipse.ui.testing;resolution:=optional Automatic-Module-Name: org.eclipse.pde.junit.runtime diff --git a/ui/org.eclipse.pde.junit.runtime/readme.txt b/ui/org.eclipse.pde.junit.runtime/readme.txt new file mode 100644 index 00000000000..db91fff86b5 --- /dev/null +++ b/ui/org.eclipse.pde.junit.runtime/readme.txt @@ -0,0 +1,18 @@ + This PDE JUnit runtime is added to test-runtimes launched from an Eclipse workspace and allows JUnit tests to be run with an OSGi runtime. + It supports the following use cases: + 1. Headless tests (no UI, no workbench) + Runs NonUIThreadTestApplication with no testable object + 2. e4 UI tests (e4 UI, no workbench) + Runs NonUIThreadTestApplication with a testable object from e4 service + 3. UI tests run in the non UI thread (UI, workbench) + Runs NonUIThreadTestApplication with a testable object from e4 service or PlatformUI + 4. UI tests run in the UI thread (UI, workbench) + Runs UITestApplication with a testable object from e4 service or PlatformUI + 5. Headless tests with no application (no UI, no workbench, no application) + Runs directly with no application + +If no pde.junit.runtime is available in the Target-Platform the one from the running Eclipse is added to the test-runtime. +Of course users can target older Eclipse versions (which for example require older Java-versions), +which is why the requirements of this Plug-in should be as low as possible to make it resolve even in older runtimes. + +If changes are made, one should ensure that the requirement's lower-bounds specified in the MANIFEST.MF are still valid. diff --git a/ui/org.eclipse.pde.junit.runtime/src/org/eclipse/pde/internal/junit/runtime/MultiBundleClassLoader.java b/ui/org.eclipse.pde.junit.runtime/src/org/eclipse/pde/internal/junit/runtime/MultiBundleClassLoader.java index 7aabec7631b..84e0bf30f2e 100644 --- a/ui/org.eclipse.pde.junit.runtime/src/org/eclipse/pde/internal/junit/runtime/MultiBundleClassLoader.java +++ b/ui/org.eclipse.pde.junit.runtime/src/org/eclipse/pde/internal/junit/runtime/MultiBundleClassLoader.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018 IBM Corporation and others. + * Copyright (c) 2018, 2023 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -36,8 +36,9 @@ protected Class findClass(String name) throws ClassNotFoundException { for (Bundle temp : bundleList) { try { Class c = temp.loadClass(name); - if (c != null) + if (c != null) { return c; + } } catch (ClassNotFoundException e) { } } @@ -46,9 +47,8 @@ protected Class findClass(String name) throws ClassNotFoundException { @Override protected URL findResource(String name) { - URL url = null; for (Bundle temp : bundleList) { - url = temp.getResource(name); + URL url = temp.getResource(name); if (url != null) { try { return FileLocator.resolve(url); @@ -57,7 +57,7 @@ protected URL findResource(String name) { } } } - return url; + return null; } @Override diff --git a/ui/org.eclipse.pde.junit.runtime/src/org/eclipse/pde/internal/junit/runtime/NonUIThreadTestApplication.java b/ui/org.eclipse.pde.junit.runtime/src/org/eclipse/pde/internal/junit/runtime/NonUIThreadTestApplication.java index ef7e9276ba9..25773b4d4f6 100644 --- a/ui/org.eclipse.pde.junit.runtime/src/org/eclipse/pde/internal/junit/runtime/NonUIThreadTestApplication.java +++ b/ui/org.eclipse.pde.junit.runtime/src/org/eclipse/pde/internal/junit/runtime/NonUIThreadTestApplication.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2015 EclipseSource Corporation and others. + * Copyright (c) 2009, 2023 EclipseSource Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -13,6 +13,8 @@ *******************************************************************************/ package org.eclipse.pde.internal.junit.runtime; +import java.util.Objects; + import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExtension; @@ -20,7 +22,10 @@ import org.eclipse.core.runtime.Platform; import org.eclipse.equinox.app.IApplication; import org.eclipse.equinox.app.IApplicationContext; -import org.junit.Assert; +import org.eclipse.ui.testing.TestableObject; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; /** * A Workbench that runs a test suite specified in the @@ -32,14 +37,15 @@ public class NonUIThreadTestApplication implements IApplication { protected IApplication fApplication; protected Object fTestHarness; + protected boolean runInUIThreadAndRequirePlatformUI = false; + protected String defaultApplicationId = DEFAULT_HEADLESSAPP; @Override public Object start(IApplicationContext context) throws Exception { String[] args = (String[]) context.getArguments().get(IApplicationContext.APPLICATION_ARGS); String appId = getApplicationToRun(args); - IApplication app = getApplication(appId); - Assert.assertNotNull(app); + IApplication app = Objects.requireNonNull(getApplication(appId)); if (!DEFAULT_HEADLESSAPP.equals(appId)) { // this means we are running a different application, which potentially can be UI application; @@ -48,11 +54,6 @@ public Object start(IApplicationContext context) throws Exception { // (see bug 340906 for details) installPlatformUITestHarness(); } - - return runApp(app, context); - } - - protected Object runApp(IApplication app, IApplicationContext context) throws Exception { fApplication = app; return fApplication.start(context); } @@ -66,27 +67,53 @@ protected Object runApp(IApplication app, IApplicationContext context) throws Ex * * @throws Exception */ - private void installPlatformUITestHarness() throws Exception { - Object testableObject = PDEJUnitRuntimePlugin.getDefault().getTestableObject(); + private void installPlatformUITestHarness() throws ReflectiveOperationException { + Object testableObject = getRegisteredTestableObject(); if (testableObject == null) { - try { + try { // If the service doesn't return a testable object ask PlatformUI directly Class platformUIClass = Class.forName("org.eclipse.ui.PlatformUI", true, getClass().getClassLoader()); //$NON-NLS-1$ testableObject = platformUIClass.getMethod("getTestableObject").invoke(null); //$NON-NLS-1$ - } catch (ClassNotFoundException e) { - // PlatformUI is not available + } catch (ClassNotFoundException e) { // PlatformUI is not available + if (runInUIThreadAndRequirePlatformUI) { + throw e; + } } } if (testableObject != null) { - fTestHarness = new PlatformUITestHarness(testableObject, true); + fTestHarness = new PlatformUITestHarness(testableObject, !runInUIThreadAndRequirePlatformUI); + } + } + + /** + * Returns a {@link TestableObject} provided by a TestableObject service or {@code null} if no implementation can be found. + * The TestableObject is used to hook tests into the application lifecycle. + *

+ * It is recommended the testable object is obtained via service instead Workbench#getWorkbenchTestable() to avoid + * the tests having a dependency on the Workbench. + *

+ * @return TestableObject provided via service or {@code null} + */ + private static Object getRegisteredTestableObject() { + BundleContext context = FrameworkUtil.getBundle(NonUIThreadTestApplication.class).getBundleContext(); + ServiceReference reference = context.getServiceReference("org.eclipse.ui.testing.TestableObject"); //$NON-NLS-1$ + if (reference != null) { + try { + return context.getService(reference); + } finally { + context.ungetService(reference); + } } + return null; } @Override public void stop() { - if (fApplication != null) + if (fApplication != null) { fApplication.stop(); - if (fTestHarness != null) + } + if (fTestHarness != null) { fTestHarness = null; + } } /* @@ -98,8 +125,7 @@ private IApplication getApplication(String appId) throws CoreException { // If no application is specified, the 3.0 default workbench application // is returned. IExtension extension = Platform.getExtensionRegistry().getExtension(Platform.PI_RUNTIME, Platform.PT_APPLICATIONS, appId); - - Assert.assertNotNull(extension); + Objects.requireNonNull(extension); // If the extension does not have the correct grammar, return null. // Otherwise, return the application object. @@ -108,8 +134,9 @@ private IApplication getApplication(String appId) throws CoreException { IConfigurationElement[] runs = elements[0].getChildren("run"); //$NON-NLS-1$ if (runs.length > 0) { Object runnable = runs[0].createExecutableExtension("class"); //$NON-NLS-1$ - if (runnable instanceof IApplication) + if (runnable instanceof IApplication) { return (IApplication) runnable; + } } } return null; @@ -125,18 +152,12 @@ private IApplication getApplication(String appId) throws CoreException { * */ private String getApplicationToRun(String[] args) { - for (int i = 0; i < args.length; i++) { - if (args[i].equals("-testApplication") && i < args.length - 1) //$NON-NLS-1$ - return args[i + 1]; + String testApp = RemotePluginTestRunner.getArgumentValue(args, "-testApplication"); //$NON-NLS-1$ + if (testApp != null) { + return testApp; } IProduct product = Platform.getProduct(); - if (product != null) - return product.getApplication(); - return getDefaultApplicationId(); - } - - protected String getDefaultApplicationId() { - return DEFAULT_HEADLESSAPP; + return product != null ? product.getApplication() : defaultApplicationId; } } diff --git a/ui/org.eclipse.pde.junit.runtime/src/org/eclipse/pde/internal/junit/runtime/PDEJUnitRuntimePlugin.java b/ui/org.eclipse.pde.junit.runtime/src/org/eclipse/pde/internal/junit/runtime/PDEJUnitRuntimePlugin.java deleted file mode 100644 index 22e8747c561..00000000000 --- a/ui/org.eclipse.pde.junit.runtime/src/org/eclipse/pde/internal/junit/runtime/PDEJUnitRuntimePlugin.java +++ /dev/null @@ -1,113 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2013, 2015 IBM Corporation 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: - * IBM Corporation - initial API and implementation - *******************************************************************************/ -package org.eclipse.pde.internal.junit.runtime; - -import org.eclipse.ui.plugin.AbstractUIPlugin; -import org.eclipse.ui.testing.TestableObject; -import org.osgi.framework.BundleActivator; -import org.osgi.framework.BundleContext; -import org.osgi.util.tracker.ServiceTracker; - -/** - * The plug-in activator for the PDE JUnit Runtime plug-in. - *

- * The PDE JUnit runtime allows JUnit tests to be run with an OSGi runtime. - * It supports the following use cases: - *

- * 1) Headless tests (no UI, no workbench)
- * 	  Runs NonUIThreadTestApplication with no testable object
- *
- * 2) e4 UI tests (e4 UI, no workbench)
- *    Runs NonUIThreadTestApplication with a testable object from e4 service
- *
- * 3) UI tests run in the non UI thread (UI, workbench)
- *    Runs NonUIThreadTestApplication with a testable object from e4 service or PlatformUI
- *
- * 4) UI tests run in the UI thread (UI, workbench)
- *    Runs UITestApplication with a testable object from e4 service or PlatformUI
- *
- * 5) Headless tests with no application (no UI, no workbench, no application)
- *    Runs directly with no application
- * 
- * @since 4.3 - */ -public class PDEJUnitRuntimePlugin implements BundleActivator { - - /** - * The testable object is accessed via service and a string name to avoid depending on UI code. The - */ - private static final String TESTABLE_OBJECT_SERVICE_NAME = "org.eclipse.ui.testing.TestableObject"; //$NON-NLS-1$ - - /** - * Default instance of the receiver - */ - private static PDEJUnitRuntimePlugin inst; - - /** - * The context within which this plugin was started. - */ - private BundleContext bundleContext; - - private ServiceTracker testableTracker = null; - - public PDEJUnitRuntimePlugin() { - super(); - inst = this; - } - - /** - * Return the default instance of this plug-in activator. This represents the runtime plug-in. - * @return PdeJUnitRuntimePlugin the runtime plug-in or null if the plug-in isn't started - * @see AbstractUIPlugin for the typical implementation pattern for plug-in classes. - */ - public static PDEJUnitRuntimePlugin getDefault() { - return inst; - } - - @Override - public void start(BundleContext context) throws Exception { - bundleContext = context; - } - - @Override - public void stop(BundleContext context) throws Exception { - if (testableTracker != null) { - testableTracker.close(); - testableTracker = null; - } - } - - /** - * Returns a {@link TestableObject} provided by a TestableObject - * service or null if a service implementation cannot - * be found. The TestableObject is used to hook tests into the - * application lifecycle. - *

- * It is recommended the testable object is obtained via service - * over Workbench#getWorkbenchTestable() to avoid the - * tests having a dependency on the Workbench. - *

- * @return TestableObject provided via service or null - */ - public Object getTestableObject() { - if (bundleContext == null) - return null; - if (testableTracker == null) { - testableTracker = new ServiceTracker<>(bundleContext, TESTABLE_OBJECT_SERVICE_NAME, null); - testableTracker.open(); - } - return testableTracker.getService(); - } - -} diff --git a/ui/org.eclipse.pde.junit.runtime/src/org/eclipse/pde/internal/junit/runtime/RemotePluginTestRunner.java b/ui/org.eclipse.pde.junit.runtime/src/org/eclipse/pde/internal/junit/runtime/RemotePluginTestRunner.java index 9060f8c2b59..fd223765a96 100644 --- a/ui/org.eclipse.pde.junit.runtime/src/org/eclipse/pde/internal/junit/runtime/RemotePluginTestRunner.java +++ b/ui/org.eclipse.pde.junit.runtime/src/org/eclipse/pde/internal/junit/runtime/RemotePluginTestRunner.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2003, 2022 IBM Corporation and others. + * Copyright (c) 2003, 2023 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,7 @@ import java.util.Collection; import java.util.Enumeration; import java.util.List; -import java.util.Locale; +import java.util.function.Predicate; import org.eclipse.core.runtime.Platform; import org.eclipse.jdt.internal.junit.runner.RemoteTestRunner; @@ -68,7 +68,6 @@ protected Enumeration findResources(String name) throws IOException { * * @see RemoteTestRunner */ - public static void main(String[] args) { RemotePluginTestRunner testRunner = new RemotePluginTestRunner(); testRunner.init(args); @@ -92,8 +91,9 @@ private static ClassLoader createJUnit5PluginClassLoader(String testPluginName) Bundle junit5RuntimeBundle = Platform.getBundle("org.eclipse.jdt.junit5.runtime"); //$NON-NLS-1$ List platformEngineBundles = findTestEngineBundles(); platformEngineBundles.add(testBundle); - if (junit5RuntimeBundle != null) + if (junit5RuntimeBundle != null) { platformEngineBundles.add(junit5RuntimeBundle); + } return new MultiBundleClassLoader(platformEngineBundles); } @@ -135,8 +135,7 @@ public ClassLoader getClassLoader(final String bundleId) { @Override public void init(String[] args) { readPluginArgs(args); - boolean isJUnit5 = isJUnit5(args); - if (isJUnit5) { + if (isJUnit5(args)) { // changing the classloader to get the testengines for junit5 // during initialization - see bug 520811 ClassLoader currentTCCL = Thread.currentThread().getContextClassLoader(); @@ -151,40 +150,23 @@ public void init(String[] args) { defaultInit(args); } - private static boolean runAsJUnit5(String[] args) { - for (String string : args) { - if (string.equalsIgnoreCase("-runasjunit5")) { //$NON-NLS-1$ - return true; - } - } - return false; - } - + @SuppressWarnings("nls") private static boolean isJUnit5(String[] args) { - if (runAsJUnit5(args) == true) - return true; - - for (int i = 0; i < args.length; i++) { - if (args[i].equals("org.eclipse.jdt.internal.junit5.runner.JUnit5TestLoader")) //$NON-NLS-1$ - return true; - } - return false; + return indexOf(args, "-runasjunit5"::equalsIgnoreCase) > -1 || indexOf(args, "org.eclipse.jdt.internal.junit5.runner.JUnit5TestLoader"::equals) > -1; } public void readPluginArgs(String[] args) { - for (int i = 0; i < args.length; i++) { - if (isFlag(args, i, "-testpluginname")) //$NON-NLS-1$ - setTestPluginName(args[i + 1]); - - if (isFlag(args, i, "-loaderpluginname")) //$NON-NLS-1$ - fLoaderClassLoader = getClassLoader(args[i + 1]); + fTestPluginName = getArgumentValue(args, "-testpluginname"); //$NON-NLS-1$ + String loaderPlugin = getArgumentValue(args, "-loaderpluginname"); //$NON-NLS-1$ + if (loaderPlugin != null) { + fLoaderClassLoader = getClassLoader(loaderPlugin); } - - if (getTestPluginName() == null) + if (getTestPluginName() == null) { throw new IllegalArgumentException("Parameter -testpluginnname not specified."); //$NON-NLS-1$ - - if (fLoaderClassLoader == null) + } + if (fLoaderClassLoader == null) { fLoaderClassLoader = getClass().getClassLoader(); + } } @Override @@ -192,16 +174,21 @@ protected Class loadTestLoaderClass(String className) throws ClassNotFoundExc return fLoaderClassLoader.loadClass(className); } - private boolean isFlag(String[] args, int i, final String wantedFlag) { - String lowerCase = args[i].toLowerCase(Locale.ENGLISH); - return lowerCase.equals(wantedFlag) && i < args.length - 1; + static String getArgumentValue(String[] arguments, String key) { + int index = indexOf(arguments, key::equalsIgnoreCase); + return 0 <= index && (index + 1) < arguments.length ? arguments[index + 1] : null; } - public String getTestPluginName() { - return fTestPluginName; + private static int indexOf(String[] args, Predicate isKey) { + for (int i = 0; i < args.length; i++) { + if (isKey.test(args[i])) { + return i; + } + } + return -1; } - public void setTestPluginName(String fTestPluginName) { - this.fTestPluginName = fTestPluginName; + public String getTestPluginName() { + return fTestPluginName; } } diff --git a/ui/org.eclipse.pde.junit.runtime/src/org/eclipse/pde/internal/junit/runtime/UITestApplication.java b/ui/org.eclipse.pde.junit.runtime/src/org/eclipse/pde/internal/junit/runtime/UITestApplication.java index ca9640b31d5..25d63741da9 100644 --- a/ui/org.eclipse.pde.junit.runtime/src/org/eclipse/pde/internal/junit/runtime/UITestApplication.java +++ b/ui/org.eclipse.pde.junit.runtime/src/org/eclipse/pde/internal/junit/runtime/UITestApplication.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2003, 2015 IBM Corporation and others. + * Copyright (c) 2003, 2023 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -14,36 +14,17 @@ *******************************************************************************/ package org.eclipse.pde.internal.junit.runtime; -import org.eclipse.equinox.app.IApplication; -import org.eclipse.equinox.app.IApplicationContext; -import org.eclipse.ui.PlatformUI; - /** * A Workbench that runs a test suite specified in the * command line arguments. */ public class UITestApplication extends NonUIThreadTestApplication { - private static final String DEFAULT_APP_3_0 = "org.eclipse.ui.ide.workbench"; //$NON-NLS-1$ - - @Override - protected String getDefaultApplicationId() { + public UITestApplication() { + // Unlike in NonUIThreadTestApplication if the platform dependency is not available we will fail the launch + this.runInUIThreadAndRequirePlatformUI = true; // In 3.0, the default is the "org.eclipse.ui.ide.worbench" application. - return DEFAULT_APP_3_0; + this.defaultApplicationId = "org.eclipse.ui.ide.workbench"; //$NON-NLS-1$ } - @Override - protected Object runApp(IApplication app, IApplicationContext context) throws Exception { - // Get the testable object from the service - Object testableObject = PDEJUnitRuntimePlugin.getDefault().getTestableObject(); - // If the service doesn't return a testable object ask PlatformUI directly - // Unlike in NonUIThreadTestApplication if the platform dependency is not available we will fail here - if (testableObject == null) { - testableObject = PlatformUI.getTestableObject(); - } - fTestHarness = new PlatformUITestHarness(testableObject, false); - - // continue application launch - return super.runApp(app, context); - } }