From 30cf7474d9fc6ff76fa49d752a6cf43c6a68f233 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Kubitz?= Date: Mon, 4 Nov 2024 10:37:46 +0100 Subject: [PATCH] How to use junit.jupiter Extensions in OSGi and plain java logs start, stop, duration, error for all tests executed --- .../org.eclipse.test/META-INF/MANIFEST.MF | 7 ++- .../org.junit.jupiter.api.extension.Extension | 2 + .../src/junit-platform.properties | 1 + .../test/services/LoggingTestExtension.java | 56 +++++++++++++++++ .../services/LoggingTestExtensionTest.java | 50 +++++++++++++++ .../services/LoggingTestExtensionTest2.java | 31 +++++++++ .../test/services/SwtLeakTestExtension.java | 63 +++++++++++++++++++ .../services/SwtLeakTestExtensionTest.java | 48 ++++++++++++++ 8 files changed, 256 insertions(+), 2 deletions(-) create mode 100644 eclipse.platform.releng/bundles/org.eclipse.test/src/META-INF/services/org.junit.jupiter.api.extension.Extension create mode 100644 eclipse.platform.releng/bundles/org.eclipse.test/src/junit-platform.properties create mode 100644 eclipse.platform.releng/bundles/org.eclipse.test/src/org/eclipse/test/services/LoggingTestExtension.java create mode 100644 eclipse.platform.releng/bundles/org.eclipse.test/src/org/eclipse/test/services/LoggingTestExtensionTest.java create mode 100644 eclipse.platform.releng/bundles/org.eclipse.test/src/org/eclipse/test/services/LoggingTestExtensionTest2.java create mode 100644 eclipse.platform.releng/bundles/org.eclipse.test/src/org/eclipse/test/services/SwtLeakTestExtension.java create mode 100644 eclipse.platform.releng/bundles/org.eclipse.test/src/org/eclipse/test/services/SwtLeakTestExtensionTest.java diff --git a/eclipse.platform.releng/bundles/org.eclipse.test/META-INF/MANIFEST.MF b/eclipse.platform.releng/bundles/org.eclipse.test/META-INF/MANIFEST.MF index f2ab114d6cf..d7097fc1b87 100644 --- a/eclipse.platform.releng/bundles/org.eclipse.test/META-INF/MANIFEST.MF +++ b/eclipse.platform.releng/bundles/org.eclipse.test/META-INF/MANIFEST.MF @@ -11,7 +11,9 @@ Require-Bundle: org.apache.ant, org.eclipse.core.runtime, org.eclipse.ui.ide.application, org.eclipse.equinox.app, - org.junit + org.apache.aries.spifly.dynamic.bundle, + junit-jupiter-api, + org.junit Import-Package: org.junit.jupiter.api, org.junit.platform.engine, org.junit.platform.engine.discovery, @@ -24,5 +26,6 @@ Import-Package: org.junit.jupiter.api, org.junit.jupiter.engine Bundle-ActivationPolicy: lazy Bundle-RequiredExecutionEnvironment: JavaSE-17 -Export-Package: org.eclipse.test +Export-Package: org.eclipse.test, + org.eclipse.test.services Automatic-Module-Name: org.eclipse.test diff --git a/eclipse.platform.releng/bundles/org.eclipse.test/src/META-INF/services/org.junit.jupiter.api.extension.Extension b/eclipse.platform.releng/bundles/org.eclipse.test/src/META-INF/services/org.junit.jupiter.api.extension.Extension new file mode 100644 index 00000000000..4c70584e733 --- /dev/null +++ b/eclipse.platform.releng/bundles/org.eclipse.test/src/META-INF/services/org.junit.jupiter.api.extension.Extension @@ -0,0 +1,2 @@ +org.eclipse.test.services.LoggingTestExtension +org.eclipse.test.services.SwtLeakTestExtension \ No newline at end of file diff --git a/eclipse.platform.releng/bundles/org.eclipse.test/src/junit-platform.properties b/eclipse.platform.releng/bundles/org.eclipse.test/src/junit-platform.properties new file mode 100644 index 00000000000..b059a65dc46 --- /dev/null +++ b/eclipse.platform.releng/bundles/org.eclipse.test/src/junit-platform.properties @@ -0,0 +1 @@ +junit.jupiter.extensions.autodetection.enabled=true \ No newline at end of file diff --git a/eclipse.platform.releng/bundles/org.eclipse.test/src/org/eclipse/test/services/LoggingTestExtension.java b/eclipse.platform.releng/bundles/org.eclipse.test/src/org/eclipse/test/services/LoggingTestExtension.java new file mode 100644 index 00000000000..0ac2e6b033f --- /dev/null +++ b/eclipse.platform.releng/bundles/org.eclipse.test/src/org/eclipse/test/services/LoggingTestExtension.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2024 Joerg Kubitz 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: + * Joerg Kubitz - initial API and implementation + *******************************************************************************/ +package org.eclipse.test.services; + +import org.junit.jupiter.api.extension.AfterTestExecutionCallback; +import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ExtensionContext.Store; + +/** logs start, stop, duration, error for all tests executed **/ +public class LoggingTestExtension implements AfterTestExecutionCallback, BeforeTestExecutionCallback { + public static String BEFORE_TEST_START = "Test Before: "; + public static String AFTER_TEST_PASSED = "Test Passed: "; + public static String AFTER_TEST_FAILED = "Test Failed: "; + + public LoggingTestExtension() { + System.out.println("LoggingTestService"); + } + @Override + public void beforeTestExecution(ExtensionContext context) throws Exception { + long n0 = System.nanoTime(); + getStore(context).put("TIME", n0); + System.out.println(BEFORE_TEST_START + context.getDisplayName()); + } + + @Override + public void afterTestExecution(ExtensionContext context) throws Exception { + long t1 = System.nanoTime(); + long t0 = getStore(context).remove("TIME", long.class); + String took = " after: " + (t1 - t0) / 1_000_000 + "ms"; + Throwable t = context.getExecutionException().orElse(null); + if (t == null) { + System.out.println(AFTER_TEST_PASSED + context.getDisplayName() + took); + } else { + System.out.println(AFTER_TEST_FAILED + context.getDisplayName() + took); + t.printStackTrace(System.out); + } + } + + private Store getStore(ExtensionContext context) { + return context.getStore(Namespace.create(getClass(), context.getRequiredTestMethod())); + } + +} diff --git a/eclipse.platform.releng/bundles/org.eclipse.test/src/org/eclipse/test/services/LoggingTestExtensionTest.java b/eclipse.platform.releng/bundles/org.eclipse.test/src/org/eclipse/test/services/LoggingTestExtensionTest.java new file mode 100644 index 00000000000..e64213412ba --- /dev/null +++ b/eclipse.platform.releng/bundles/org.eclipse.test/src/org/eclipse/test/services/LoggingTestExtensionTest.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2024 Joerg Kubitz 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: + * Joerg Kubitz - initial API and implementation + *******************************************************************************/ +package org.eclipse.test.services; + +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** verifies the Extension is used **/ +public class LoggingTestExtensionTest { + private static final String SOMETHING = "something"; + private static final ByteArrayOutputStream OUT = new ByteArrayOutputStream(); + private static PrintStream oldStdOut; + + @BeforeAll + public static void beforeAll() { + oldStdOut = System.out; + System.setOut(new PrintStream(OUT)); + } + @Test + public void testWritten() { + System.out.println(SOMETHING); + } + + @AfterAll + public static void afterAll() { + String output = new String(OUT.toByteArray()); + System.setOut(oldStdOut); + assertTrue(output, output.contains("testWritten")); + assertTrue(output, output.contains(SOMETHING)); + assertTrue(output, output.contains(org.eclipse.test.services.LoggingTestExtension.BEFORE_TEST_START)); + assertTrue(output, output.contains(org.eclipse.test.services.LoggingTestExtension.AFTER_TEST_PASSED)); + } +} diff --git a/eclipse.platform.releng/bundles/org.eclipse.test/src/org/eclipse/test/services/LoggingTestExtensionTest2.java b/eclipse.platform.releng/bundles/org.eclipse.test/src/org/eclipse/test/services/LoggingTestExtensionTest2.java new file mode 100644 index 00000000000..97fdd35bedf --- /dev/null +++ b/eclipse.platform.releng/bundles/org.eclipse.test/src/org/eclipse/test/services/LoggingTestExtensionTest2.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2024 Joerg Kubitz 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: + * Joerg Kubitz - initial API and implementation + *******************************************************************************/ +package org.eclipse.test.services; + +import org.junit.jupiter.api.Test; + +// Manual demonstration: +public class LoggingTestExtensionTest2 { + + @Test + public void testWritten() { + System.out.println("real"); + } + + @Test + public void testWritten2() { + throw new RuntimeException("intended"); + } + +} diff --git a/eclipse.platform.releng/bundles/org.eclipse.test/src/org/eclipse/test/services/SwtLeakTestExtension.java b/eclipse.platform.releng/bundles/org.eclipse.test/src/org/eclipse/test/services/SwtLeakTestExtension.java new file mode 100644 index 00000000000..35c24b9ec67 --- /dev/null +++ b/eclipse.platform.releng/bundles/org.eclipse.test/src/org/eclipse/test/services/SwtLeakTestExtension.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2024 Joerg Kubitz 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: + * Joerg Kubitz - initial API and implementation + *******************************************************************************/ +package org.eclipse.test.services; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.PlatformUI; +import org.junit.jupiter.api.extension.AfterTestExecutionCallback; +import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ExtensionContext.Store; + +/** logs start, stop, duration, error for all tests executed **/ +public class SwtLeakTestExtension implements AfterTestExecutionCallback, BeforeTestExecutionCallback { + @Override + public void beforeTestExecution(ExtensionContext context) throws Exception { + IWorkbench workbench = PlatformUI.getWorkbench(); + Set preExistingShells = Set.of(workbench.getDisplay().getShells()); + + getStore(context).put("workbench", workbench); + getStore(context).put("preExistingShells", preExistingShells); + } + + @Override + public void afterTestExecution(ExtensionContext context) throws Exception { + IWorkbench workbench = getStore(context).remove("workbench", IWorkbench.class); + Set preExistingShells = getStore(context).remove("preExistingShells", Set.class); + // Check for shell leak. + List leakedModalShellTitles = new ArrayList<>(); + Shell[] shells = workbench.getDisplay().getShells(); + for (Shell shell : shells) { + if (!shell.isDisposed() && !preExistingShells.contains(shell)) { + leakedModalShellTitles.add(shell.getText()); + shell.close(); + } + } + assertEquals("Test leaked modal shell: [" + String.join(", ", leakedModalShellTitles) + "]", 0, + leakedModalShellTitles.size()); + } + + private Store getStore(ExtensionContext context) { + return context.getStore(Namespace.create(getClass(), context.getRequiredTestMethod())); + } + +} diff --git a/eclipse.platform.releng/bundles/org.eclipse.test/src/org/eclipse/test/services/SwtLeakTestExtensionTest.java b/eclipse.platform.releng/bundles/org.eclipse.test/src/org/eclipse/test/services/SwtLeakTestExtensionTest.java new file mode 100644 index 00000000000..ae30325a6fc --- /dev/null +++ b/eclipse.platform.releng/bundles/org.eclipse.test/src/org/eclipse/test/services/SwtLeakTestExtensionTest.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2024 Joerg Kubitz 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: + * Joerg Kubitz - initial API and implementation + *******************************************************************************/ +package org.eclipse.test.services; + +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.PlatformUI; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; + +// Manual demonstration: +public class SwtLeakTestExtensionTest { + static Shell leakedShell; + @Test + public void testNoLeak() { + System.out.println("noleak"); + IWorkbench workbench = PlatformUI.getWorkbench(); + Display display = workbench.getDisplay(); + Shell shell = new Shell(display); + shell.dispose(); + } + + @Test + public void testLeak() { // fails + System.out.println("leak"); + IWorkbench workbench = PlatformUI.getWorkbench(); + Display display = workbench.getDisplay(); + leakedShell = new Shell(display); + } + + @AfterAll + public static void afterAll() { + leakedShell.dispose(); + } + +}