diff --git a/platform/org.eclipse.platform.testing/.classpath b/platform/org.eclipse.platform.testing/.classpath
new file mode 100644
index 00000000000..375961e4d61
--- /dev/null
+++ b/platform/org.eclipse.platform.testing/.classpath
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/platform/org.eclipse.platform.testing/.project b/platform/org.eclipse.platform.testing/.project
new file mode 100644
index 00000000000..018450e2322
--- /dev/null
+++ b/platform/org.eclipse.platform.testing/.project
@@ -0,0 +1,34 @@
+
+
+ org.eclipse.platform.testing
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.pde.ManifestBuilder
+
+
+
+
+ org.eclipse.pde.SchemaBuilder
+
+
+
+
+ org.eclipse.pde.api.tools.apiAnalysisBuilder
+
+
+
+
+
+ org.eclipse.pde.PluginNature
+ org.eclipse.jdt.core.javanature
+ org.eclipse.pde.api.tools.apiAnalysisNature
+
+
diff --git a/platform/org.eclipse.platform.testing/.settings/.api_filters b/platform/org.eclipse.platform.testing/.settings/.api_filters
new file mode 100644
index 00000000000..258bc415389
--- /dev/null
+++ b/platform/org.eclipse.platform.testing/.settings/.api_filters
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/platform/org.eclipse.platform.testing/.settings/org.eclipse.core.resources.prefs b/platform/org.eclipse.platform.testing/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 00000000000..99f26c0203a
--- /dev/null
+++ b/platform/org.eclipse.platform.testing/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+encoding/=UTF-8
diff --git a/platform/org.eclipse.platform.testing/.settings/org.eclipse.jdt.core.prefs b/platform/org.eclipse.platform.testing/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 00000000000..23fa13b1705
--- /dev/null
+++ b/platform/org.eclipse.platform.testing/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,9 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=21
+org.eclipse.jdt.core.compiler.compliance=21
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
+org.eclipse.jdt.core.compiler.release=enabled
+org.eclipse.jdt.core.compiler.source=21
diff --git a/platform/org.eclipse.platform.testing/.settings/org.eclipse.pde.core.prefs b/platform/org.eclipse.platform.testing/.settings/org.eclipse.pde.core.prefs
new file mode 100644
index 00000000000..e8ff8be0bab
--- /dev/null
+++ b/platform/org.eclipse.platform.testing/.settings/org.eclipse.pde.core.prefs
@@ -0,0 +1,4 @@
+eclipse.preferences.version=1
+pluginProject.equinox=false
+pluginProject.extensions=false
+resolve.requirebundle=false
diff --git a/platform/org.eclipse.platform.testing/META-INF/MANIFEST.MF b/platform/org.eclipse.platform.testing/META-INF/MANIFEST.MF
new file mode 100644
index 00000000000..9f91a0e929a
--- /dev/null
+++ b/platform/org.eclipse.platform.testing/META-INF/MANIFEST.MF
@@ -0,0 +1,17 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Testing Support for Eclipse Applications
+Bundle-SymbolicName: org.eclipse.platform.testing
+Bundle-Version: 1.0.0.qualifier
+Automatic-Module-Name: org.eclipse.platform.testing
+Bundle-ActivationPolicy: lazy
+Bundle-RequiredExecutionEnvironment: JavaSE-21
+Import-Package: org.eclipse.osgi.service.runnable;version="[1.1.0,2.0.0)",
+ org.junit.jupiter.api.extension;version="[5.14.0,6.0.0)",
+ org.junit.platform.launcher;version="[1.14.0,2.0.0)",
+ org.osgi.framework;version="[1.10.0,2.0.0)",
+ org.osgi.util.tracker;version="[1.5.0,2.0.0)"
+Require-Bundle: org.eclipse.equinox.common;bundle-version="3.20.300",
+ org.eclipse.e4.ui.workbench3,
+ org.eclipse.equinox.app
+Export-Package: org.eclipse.platform.testing
diff --git a/platform/org.eclipse.platform.testing/build.properties b/platform/org.eclipse.platform.testing/build.properties
new file mode 100644
index 00000000000..07c72fb6e09
--- /dev/null
+++ b/platform/org.eclipse.platform.testing/build.properties
@@ -0,0 +1,5 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .
+tycho.pomless.parent = ../../
\ No newline at end of file
diff --git a/platform/org.eclipse.platform.testing/src/META-INF/services/org.junit.jupiter.api.extension.Extension b/platform/org.eclipse.platform.testing/src/META-INF/services/org.junit.jupiter.api.extension.Extension
new file mode 100644
index 00000000000..12706196285
--- /dev/null
+++ b/platform/org.eclipse.platform.testing/src/META-INF/services/org.junit.jupiter.api.extension.Extension
@@ -0,0 +1 @@
+org.eclipse.platform.testing.EclipseInvocationInterceptor
\ No newline at end of file
diff --git a/platform/org.eclipse.platform.testing/src/META-INF/services/org.junit.platform.launcher.LauncherSessionListener b/platform/org.eclipse.platform.testing/src/META-INF/services/org.junit.platform.launcher.LauncherSessionListener
new file mode 100644
index 00000000000..a42efbbf175
--- /dev/null
+++ b/platform/org.eclipse.platform.testing/src/META-INF/services/org.junit.platform.launcher.LauncherSessionListener
@@ -0,0 +1 @@
+org.eclipse.platform.testing.EclipseLauncherSessionListener
\ No newline at end of file
diff --git a/platform/org.eclipse.platform.testing/src/org/eclipse/platform/testing/EclipseApplicationLauncher.java b/platform/org.eclipse.platform.testing/src/org/eclipse/platform/testing/EclipseApplicationLauncher.java
new file mode 100644
index 00000000000..ccff01ad48b
--- /dev/null
+++ b/platform/org.eclipse.platform.testing/src/org/eclipse/platform/testing/EclipseApplicationLauncher.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Christoph Läubrich 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:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.platform.testing;
+
+import org.eclipse.osgi.service.runnable.ApplicationLauncher;
+import org.eclipse.osgi.service.runnable.ParameterizedRunnable;
+
+class EclipseApplicationLauncher implements ApplicationLauncher {
+
+ @Override
+ public void launch(ParameterizedRunnable runnable, Object context) {
+
+ }
+
+ @Override
+ public void shutdown() {
+
+ }
+
+}
diff --git a/platform/org.eclipse.platform.testing/src/org/eclipse/platform/testing/EclipseInvocationInterceptor.java b/platform/org.eclipse.platform.testing/src/org/eclipse/platform/testing/EclipseInvocationInterceptor.java
new file mode 100644
index 00000000000..706017cd613
--- /dev/null
+++ b/platform/org.eclipse.platform.testing/src/org/eclipse/platform/testing/EclipseInvocationInterceptor.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Christoph Läubrich 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:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.platform.testing;
+
+import java.lang.reflect.Method;
+
+import org.eclipse.pde.api.tools.annotations.NoInstantiate;
+import org.eclipse.pde.api.tools.annotations.NoReference;
+import org.eclipse.ui.testing.TestableObject;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.InvocationInterceptor;
+import org.junit.jupiter.api.extension.ReflectiveInvocationContext;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * An {@link InvocationInterceptor} that executes all tests inside the
+ * {@link TestableObject#runTest(Runnable)} method.
+ */
+@NoInstantiate
+public class EclipseInvocationInterceptor implements InvocationInterceptor {
+
+ private final ServiceTracker testableObjectTracker;
+ private final TestableObject testableObject;
+
+ /**
+ * Creates the extension, will be called by JUnit
+ */
+ public EclipseInvocationInterceptor() {
+ Bundle bundle = FrameworkUtil.getBundle(EclipseLauncherSessionListener.class);
+ if (bundle == null) {
+ throw new IllegalStateException("Not running inside an OSGi Framework");
+ }
+ BundleContext bundleContext = bundle.getBundleContext();
+ if (bundleContext == null) {
+ throw new IllegalStateException("Extension Bundle not started");
+ }
+ testableObjectTracker = new ServiceTracker<>(bundleContext, TestableObject.class, null);
+ testableObjectTracker.open();
+ testableObject = testableObjectTracker.getService();
+ if (testableObject == null) {
+ throw new IllegalStateException("Testable Object not found!");
+ }
+ }
+
+ @Override
+ @NoReference
+ public void interceptTestMethod(Invocation invocation, ReflectiveInvocationContext invocationContext,
+ ExtensionContext extensionContext) throws Throwable {
+ Throwable[] throwable = new Throwable[1];
+ TestableObject service = testableObjectTracker.getService();
+ if (service != null) {
+ service.runTest(() -> {
+ try {
+ invocation.proceed();
+ } catch (Throwable e) {
+ throwable[0] = e;
+ }
+ });
+ Throwable t = throwable[0];
+ if (t == null) {
+ return;
+ }
+ throw t;
+ }
+ }
+
+}
diff --git a/platform/org.eclipse.platform.testing/src/org/eclipse/platform/testing/EclipseLauncherSessionListener.java b/platform/org.eclipse.platform.testing/src/org/eclipse/platform/testing/EclipseLauncherSessionListener.java
new file mode 100644
index 00000000000..9d39b57e601
--- /dev/null
+++ b/platform/org.eclipse.platform.testing/src/org/eclipse/platform/testing/EclipseLauncherSessionListener.java
@@ -0,0 +1,141 @@
+package org.eclipse.platform.testing;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.stream.Collectors;
+
+import org.eclipse.equinox.app.IApplicationContext;
+import org.eclipse.osgi.service.runnable.ApplicationLauncher;
+import org.eclipse.osgi.service.runnable.ParameterizedRunnable;
+import org.eclipse.pde.api.tools.annotations.NoInstantiate;
+import org.eclipse.pde.api.tools.annotations.NoReference;
+import org.eclipse.ui.testing.ITestHarness;
+import org.eclipse.ui.testing.TestableObject;
+import org.junit.platform.launcher.LauncherSession;
+import org.junit.platform.launcher.LauncherSessionListener;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.application.ApplicationDescriptor;
+import org.osgi.service.application.ApplicationException;
+import org.osgi.service.application.ApplicationHandle;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ *
+ */
+@NoInstantiate
+public class EclipseLauncherSessionListener implements LauncherSessionListener {
+
+ private TestableObject testableObject;
+ private ApplicationHandle applicationHandle;
+ private ServiceTracker testableObjectTracker;
+ private ServiceTracker applicationDescriptorTracker;
+ private ServiceRegistration> applicationService;
+ private Thread applicationThread;
+
+
+ @Override
+ @NoReference
+ public void launcherSessionOpened(LauncherSession session) {
+ Bundle bundle = FrameworkUtil.getBundle(EclipseLauncherSessionListener.class);
+ if (bundle == null) {
+ System.err.println("Not running inside OSGi!");
+ return;
+ }
+ BundleContext bundleContext = bundle.getBundleContext();
+ if (bundleContext == null) {
+ System.err.println("Not started/resolved?");
+ return;
+ }
+ EclipseApplicationLauncher launcher = new EclipseApplicationLauncher();
+ applicationService = bundleContext.registerService(ApplicationLauncher.class, launcher, null);
+ testableObjectTracker = new ServiceTracker<>(bundleContext, TestableObject.class, null);
+ applicationDescriptorTracker = new ServiceTracker<>(bundleContext, ApplicationDescriptor.class, null);
+ applicationDescriptorTracker.open();
+ testableObjectTracker.open();
+ String testApplication = "org.eclipse.ui.ide.workbench"; // TODO how to access the JUnit config of the
+ // session?!?
+ Map tracked = applicationDescriptorTracker.getTracked().entrySet().stream()
+ .collect(Collectors.toMap(e -> (String) e.getKey().getProperty(ApplicationDescriptor.APPLICATION_PID),
+ e -> e.getValue()));
+ ApplicationDescriptor applicationDescriptor = tracked.get(testApplication);
+ if (applicationDescriptor == null) {
+ System.err.println("Test application '" + testApplication + "' was not found available applications are:");
+ for (String appId : tracked.keySet()) {
+ System.err.println("\t- " + appId);
+ }
+ return;
+ }
+ testableObject = testableObjectTracker.getService();
+ if (testableObject == null) {
+ System.err.println("No TestableObject found!");
+ return;
+ }
+ CountDownLatch latch = new CountDownLatch(1);
+ testableObject.setTestHarness(new ITestHarness() {
+
+ @Override
+ public void runTests() {
+ latch.countDown();
+ }
+ });
+ HashMap launchArgs = new HashMap<>(1);
+ String[] args = new String[0];
+ launchArgs.put(IApplicationContext.APPLICATION_ARGS, args);
+ try {
+ applicationHandle = applicationDescriptor.launch(launchArgs);
+ } catch (ApplicationException e) {
+ e.printStackTrace();
+ return;
+ }
+ if (applicationHandle instanceof ParameterizedRunnable runable) {
+ applicationThread = new Thread(new Runnable() {
+
+ @Override
+ public void run() {
+ try {
+ runable.run(args);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ applicationThread.setName("Eclipse-Test-Application [" + testApplication + "]");
+ applicationThread.setDaemon(true);
+ applicationThread.start();
+ try {
+ latch.await();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ @Override
+ @NoReference
+ public void launcherSessionClosed(LauncherSession session) {
+ if (testableObject != null) {
+ testableObject.testingFinished();
+ }
+ if (testableObjectTracker != null) {
+ testableObjectTracker.close();
+ }
+ if (applicationDescriptorTracker != null) {
+ applicationDescriptorTracker.close();
+ }
+ if (applicationService != null) {
+ applicationService.unregister();
+ }
+ if (applicationThread != null) {
+ try {
+ applicationThread.join();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+}
diff --git a/team/tests/org.eclipse.compare.tests/META-INF/MANIFEST.MF b/team/tests/org.eclipse.compare.tests/META-INF/MANIFEST.MF
index 33ddc5aceee..3dbe4bc16b0 100644
--- a/team/tests/org.eclipse.compare.tests/META-INF/MANIFEST.MF
+++ b/team/tests/org.eclipse.compare.tests/META-INF/MANIFEST.MF
@@ -13,7 +13,9 @@ Require-Bundle: org.eclipse.compare,
org.eclipse.core.tests.resources,
org.eclipse.core.tests.harness,
org.eclipse.core.filesystem,
- org.eclipse.team.ui
+ org.eclipse.team.ui,
+ org.eclipse.platform.testing,
+ org.eclipse.ui.ide.application;bundle-version="1.5.900"
Import-Package: org.assertj.core.api,
org.assertj.core.api.iterable,
org.junit.jupiter.api;version="[5.14.0,6.0.0)",
diff --git a/team/tests/org.eclipse.compare.tests/build.properties b/team/tests/org.eclipse.compare.tests/build.properties
index 03eb29630d2..2f08ad5f80d 100644
--- a/team/tests/org.eclipse.compare.tests/build.properties
+++ b/team/tests/org.eclipse.compare.tests/build.properties
@@ -28,4 +28,4 @@ output.. = bin/
# Maven/Tycho pom model adjustments
pom.model.property.testClass = org.eclipse.compare.tests.AllCompareTests
-pom.model.property.tycho.surefire.useUIHarness = true
+pom.model.property.tycho.surefire.useUIHarness = false
diff --git a/team/tests/org.eclipse.compare.tests/pom.xml b/team/tests/org.eclipse.compare.tests/pom.xml
new file mode 100644
index 00000000000..273199daed4
--- /dev/null
+++ b/team/tests/org.eclipse.compare.tests/pom.xml
@@ -0,0 +1,89 @@
+
+
+ 4.0.0
+
+ org.eclipse.platform
+ eclipse.platform.team.tests
+ 4.38.0-SNAPSHOT
+
+ org.eclipse.compare.tests
+ eclipse-plugin
+ 3.8.900-SNAPSHOT
+
+ 6.0.0-SNAPSHOT
+
+
+
+
+
+ org.eclipse.tycho
+ tycho-surefire-plugin
+
+ true
+
+
+
+ org.eclipse.tycho
+ tycho-test-plugin
+ ${tycho.version}
+
+
+ execute-tests
+
+ junit-platform
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+ org.junit.platform
+ junit-platform-console
+ 1.14.0
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.14.0
+ test
+
+
+ org.eclipse.platform
+ org.eclipse.platform.testing
+ 1.0.0-SNAPSHOT
+ compile
+
+
+