From 5bfe6ad6c7e0bc909842f6b1c293107382e674d6 Mon Sep 17 00:00:00 2001 From: Yongjun Hong Date: Sun, 22 Dec 2024 13:32:47 +0900 Subject: [PATCH 01/55] Generate ResourceContext for store resources Issue: #2816 Signed-off-by: yongjunhong --- .../descriptor/AbstractResourceContext.java | 62 +++++++++ .../descriptor/NamespaceAwareStore.java | 91 ++++++++++++++ .../engine/support/store/ResourceContext.java | 118 ++++++++++++++++++ .../store/ResourceContextException.java | 22 ++++ 4 files changed, 293 insertions(+) create mode 100644 junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/AbstractResourceContext.java create mode 100644 junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/NamespaceAwareStore.java create mode 100644 junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/ResourceContext.java create mode 100644 junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/ResourceContextException.java diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/AbstractResourceContext.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/AbstractResourceContext.java new file mode 100644 index 000000000000..6be2c52b0be5 --- /dev/null +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/AbstractResourceContext.java @@ -0,0 +1,62 @@ +/* + * Copyright 2015-2024 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.descriptor; + +import java.util.Optional; + +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; +import org.junit.platform.engine.support.store.ResourceContext; +import org.junit.platform.engine.support.store.ResourceContext.Store.CloseableResource; + +public class AbstractResourceContext implements ResourceContext, AutoCloseable { + + private static final NamespacedHierarchicalStore.CloseAction CLOSE_RESOURCES = (__, ___, value) -> { + if (value instanceof CloseableResource) { + ((CloseableResource) value).close(); + } + }; + + private final ResourceContext parent; + private final NamespacedHierarchicalStore valueStore; + + public AbstractResourceContext(ResourceContext parent) { + this.parent = parent; + this.valueStore = createStore(parent); + } + + private static NamespacedHierarchicalStore createStore(ResourceContext parent) { + NamespacedHierarchicalStore parentStore = null; + if (parent != null) { + parentStore = ((AbstractResourceContext) parent).valueStore; + } + + return new NamespacedHierarchicalStore<>(parentStore, CLOSE_RESOURCES); + } + + @Override + public void close() throws Exception { + this.valueStore.close(); + } + + @Override + public Optional getParent() { + return Optional.ofNullable(parent); + } + + @Override + public ResourceContext getRoot() { + if (this.parent != null) { + return this.parent.getRoot(); + } + return this; + } + +} diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/NamespaceAwareStore.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/NamespaceAwareStore.java new file mode 100644 index 000000000000..032846e061d3 --- /dev/null +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/NamespaceAwareStore.java @@ -0,0 +1,91 @@ +/* + * Copyright 2015-2024 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.descriptor; + +import static org.junit.platform.engine.support.store.ResourceContext.Namespace; + +import java.util.function.Function; +import java.util.function.Supplier; + +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStoreException; +import org.junit.platform.engine.support.store.ResourceContext.Store; +import org.junit.platform.engine.support.store.ResourceContextException; + +public class NamespaceAwareStore implements Store { + + private final NamespacedHierarchicalStore valuesStore; + private final Namespace namespace; + + public NamespaceAwareStore(NamespacedHierarchicalStore valuesStore, Namespace namespace) { + this.valuesStore = valuesStore; + this.namespace = namespace; + } + + @Override + public Object get(Object key) { + Preconditions.notNull(key, "key must not be null"); + return accessStore(() -> this.valuesStore.get(this.namespace, key)); + } + + @Override + public V get(Object key, Class requiredType) { + Preconditions.notNull(key, "key must not be null"); + Preconditions.notNull(requiredType, "requiredType must not be null"); + return accessStore(() -> this.valuesStore.get(this.namespace, key, requiredType)); + } + + @Override + public Object getOrComputeIfAbsent(K key, Function defaultCreator) { + Preconditions.notNull(key, "key must not be null"); + Preconditions.notNull(defaultCreator, "defaultCreator function must not be null"); + return accessStore(() -> this.valuesStore.getOrComputeIfAbsent(this.namespace, key, defaultCreator)); + } + + @Override + public V getOrComputeIfAbsent(K key, Function defaultCreator, Class requiredType) { + Preconditions.notNull(key, "key must not be null"); + Preconditions.notNull(defaultCreator, "defaultCreator function must not be null"); + Preconditions.notNull(requiredType, "requiredType must not be null"); + return accessStore( + () -> this.valuesStore.getOrComputeIfAbsent(this.namespace, key, defaultCreator, requiredType)); + } + + @Override + public void put(Object key, Object value) { + Preconditions.notNull(key, "key must not be null"); + accessStore(() -> this.valuesStore.put(this.namespace, key, value)); + } + + @Override + public Object remove(Object key) { + Preconditions.notNull(key, "key must not be null"); + return accessStore(() -> this.valuesStore.remove(this.namespace, key)); + } + + @Override + public T remove(Object key, Class requiredType) { + Preconditions.notNull(key, "key must not be null"); + Preconditions.notNull(requiredType, "requiredType must not be null"); + return accessStore(() -> this.valuesStore.remove(this.namespace, key, requiredType)); + } + + private T accessStore(Supplier action) { + try { + return action.get(); + } + catch (NamespacedHierarchicalStoreException e) { + throw new ResourceContextException(e.getMessage(), e); + } + } + +} diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/ResourceContext.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/ResourceContext.java new file mode 100644 index 000000000000..de031b33000c --- /dev/null +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/ResourceContext.java @@ -0,0 +1,118 @@ +/* + * Copyright 2015-2024 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.store; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; + +import org.apiguardian.api.API; +import org.junit.platform.commons.support.ReflectionSupport; +import org.junit.platform.commons.util.Preconditions; + +public interface ResourceContext { + + Optional getParent(); + + ResourceContext getRoot(); + + // TODO: Implement methods to retrieve session and request + // Optional getSession(); + // Optional getRequest(); + + // TODO : Implement methods to retrieve lifecycle about session and request + // Optional getSessionLifecycle(); + // Optional getRequestLifecycle(); + + interface Store { + + @API(status = STABLE, since = "5.1") + interface CloseableResource { + + void close() throws Throwable; + + } + + Object get(Object key); + + V get(Object key, Class requiredType); + + default V getOrDefault(Object key, Class requiredType, V defaultValue) { + V value = get(key, requiredType); + return (value != null ? value : defaultValue); + } + + @API(status = STABLE, since = "5.1") + default V getOrComputeIfAbsent(Class type) { + return getOrComputeIfAbsent(type, ReflectionSupport::newInstance, type); + } + + Object getOrComputeIfAbsent(K key, Function defaultCreator); + + V getOrComputeIfAbsent(K key, Function defaultCreator, Class requiredType); + + void put(Object key, Object value); + + Object remove(Object key); + + V remove(Object key, Class requiredType); + + } + + class Namespace { + + public static final Namespace GLOBAL = Namespace.create(new Object()); + + public static Namespace create(Object... parts) { + Preconditions.notEmpty(parts, "parts array must not be null or empty"); + Preconditions.containsNoNullElements(parts, "individual parts must not be null"); + return new Namespace(new ArrayList<>(Arrays.asList(parts))); + } + + private final List parts; + + private Namespace(List parts) { + this.parts = parts; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Namespace that = (Namespace) o; + return this.parts.equals(that.parts); + } + + @Override + public int hashCode() { + return this.parts.hashCode(); + } + + public Namespace append(Object... parts) { + Preconditions.notEmpty(parts, "parts array must not be null or empty"); + Preconditions.containsNoNullElements(parts, "individual parts must not be null"); + ArrayList newParts = new ArrayList<>(this.parts.size() + parts.length); + newParts.addAll(this.parts); + Collections.addAll(newParts, parts); + return new Namespace(newParts); + } + } + +} diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/ResourceContextException.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/ResourceContextException.java new file mode 100644 index 000000000000..604725ef9b69 --- /dev/null +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/ResourceContextException.java @@ -0,0 +1,22 @@ +/* + * Copyright 2015-2024 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.store; + +public class ResourceContextException extends RuntimeException { + public ResourceContextException(String message) { + super(message); + } + + public ResourceContextException(String message, Throwable cause) { + super(message, cause); + } + +} From 7992634854d174fa98202872ffde1280f727c222 Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Sun, 22 Dec 2024 14:04:06 +0900 Subject: [PATCH 02/55] Add getStore method in LauncherSession Issue: #2816 Signed-off-by: yongjunhong --- .../org/junit/platform/launcher/LauncherSession.java | 4 ++++ .../platform/launcher/core/DefaultLauncherSession.java | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java index 92fb5e9f37ae..efab8b95458f 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java @@ -11,8 +11,10 @@ package org.junit.platform.launcher; import static org.apiguardian.api.API.Status.STABLE; +import static org.junit.platform.engine.support.store.ResourceContext.*; import org.apiguardian.api.API; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.core.LauncherFactory; /** @@ -47,4 +49,6 @@ public interface LauncherSession extends AutoCloseable { @Override void close(); + NamespacedHierarchicalStore getStore(); + } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java index 10965c631eb9..b1f1a261a901 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java @@ -10,10 +10,13 @@ package org.junit.platform.launcher.core; +import static org.junit.platform.engine.support.store.ResourceContext.*; + import java.util.List; import java.util.function.Supplier; import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; @@ -43,6 +46,7 @@ public void close() { private final LauncherInterceptor interceptor; private final LauncherSessionListener listener; private final DelegatingLauncher launcher; + private final NamespacedHierarchicalStore store; DefaultLauncherSession(List interceptors, Supplier listenerSupplier, Supplier launcherSupplier) { @@ -58,6 +62,7 @@ public void close() { } this.launcher = new DelegatingLauncher(launcher); listener.launcherSessionOpened(this); + this.store = new NamespacedHierarchicalStore<>(null); } @Override @@ -78,6 +83,11 @@ public void close() { } } + @Override + public NamespacedHierarchicalStore getStore() { + return store; + } + private static class ClosedLauncher implements Launcher { static final ClosedLauncher INSTANCE = new ClosedLauncher(); From f3adab18fd84d03a31f9e682ce10446b5078b6b7 Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Mon, 6 Jan 2025 21:17:31 +0900 Subject: [PATCH 03/55] Add Namespace by reusing Jupiter's Namespace class Issue: #2816 --- .../org/junit/platform/engine/Namespace.java | 85 +++++++++++++ .../descriptor/AbstractResourceContext.java | 62 --------- .../descriptor/NamespaceAwareStore.java | 91 -------------- .../engine/support/store/ResourceContext.java | 118 ------------------ .../store/ResourceContextException.java | 22 ---- .../platform/launcher/LauncherSession.java | 2 +- .../launcher/core/DefaultLauncherSession.java | 3 +- 7 files changed, 87 insertions(+), 296 deletions(-) create mode 100644 junit-platform-engine/src/main/java/org/junit/platform/engine/Namespace.java delete mode 100644 junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/AbstractResourceContext.java delete mode 100644 junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/NamespaceAwareStore.java delete mode 100644 junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/ResourceContext.java delete mode 100644 junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/ResourceContextException.java diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/Namespace.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/Namespace.java new file mode 100644 index 000000000000..b97bf19a341a --- /dev/null +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/Namespace.java @@ -0,0 +1,85 @@ +/* + * Copyright 2015-2024 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; + +@API(status = STABLE, since = "5.13") +public class Namespace { + + /** + * The default, global namespace which allows access to stored data from + * all extensions. + */ + public static final Namespace GLOBAL = Namespace.create(new Object()); + + /** + * Create a namespace which restricts access to data to all extensions + * which use the same sequence of {@code parts} for creating a namespace. + * + *

The order of the {@code parts} is significant. + * + *

Internally the {@code parts} are compared using {@link Object#equals(Object)}. + */ + public static Namespace create(Object... parts) { + Preconditions.notEmpty(parts, "parts array must not be null or empty"); + Preconditions.containsNoNullElements(parts, "individual parts must not be null"); + return new Namespace(new ArrayList<>(Arrays.asList(parts))); + } + + private final List parts; + + private Namespace(List parts) { + this.parts = parts; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Namespace that = (Namespace) o; + return this.parts.equals(that.parts); + } + + @Override + public int hashCode() { + return this.parts.hashCode(); + } + + /** + * Create a new namespace by appending the supplied {@code parts} to the + * existing sequence of parts in this namespace. + * + * @return new namespace; never {@code null} + * @since 5.8 + */ + @API(status = STABLE, since = "5.13") + public Namespace append(Object... parts) { + Preconditions.notEmpty(parts, "parts array must not be null or empty"); + Preconditions.containsNoNullElements(parts, "individual parts must not be null"); + ArrayList newParts = new ArrayList<>(this.parts.size() + parts.length); + newParts.addAll(this.parts); + Collections.addAll(newParts, parts); + return new Namespace(newParts); + } +} diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/AbstractResourceContext.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/AbstractResourceContext.java deleted file mode 100644 index 6be2c52b0be5..000000000000 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/AbstractResourceContext.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2015-2024 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.descriptor; - -import java.util.Optional; - -import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; -import org.junit.platform.engine.support.store.ResourceContext; -import org.junit.platform.engine.support.store.ResourceContext.Store.CloseableResource; - -public class AbstractResourceContext implements ResourceContext, AutoCloseable { - - private static final NamespacedHierarchicalStore.CloseAction CLOSE_RESOURCES = (__, ___, value) -> { - if (value instanceof CloseableResource) { - ((CloseableResource) value).close(); - } - }; - - private final ResourceContext parent; - private final NamespacedHierarchicalStore valueStore; - - public AbstractResourceContext(ResourceContext parent) { - this.parent = parent; - this.valueStore = createStore(parent); - } - - private static NamespacedHierarchicalStore createStore(ResourceContext parent) { - NamespacedHierarchicalStore parentStore = null; - if (parent != null) { - parentStore = ((AbstractResourceContext) parent).valueStore; - } - - return new NamespacedHierarchicalStore<>(parentStore, CLOSE_RESOURCES); - } - - @Override - public void close() throws Exception { - this.valueStore.close(); - } - - @Override - public Optional getParent() { - return Optional.ofNullable(parent); - } - - @Override - public ResourceContext getRoot() { - if (this.parent != null) { - return this.parent.getRoot(); - } - return this; - } - -} diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/NamespaceAwareStore.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/NamespaceAwareStore.java deleted file mode 100644 index 032846e061d3..000000000000 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/NamespaceAwareStore.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2015-2024 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.descriptor; - -import static org.junit.platform.engine.support.store.ResourceContext.Namespace; - -import java.util.function.Function; -import java.util.function.Supplier; - -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; -import org.junit.platform.engine.support.store.NamespacedHierarchicalStoreException; -import org.junit.platform.engine.support.store.ResourceContext.Store; -import org.junit.platform.engine.support.store.ResourceContextException; - -public class NamespaceAwareStore implements Store { - - private final NamespacedHierarchicalStore valuesStore; - private final Namespace namespace; - - public NamespaceAwareStore(NamespacedHierarchicalStore valuesStore, Namespace namespace) { - this.valuesStore = valuesStore; - this.namespace = namespace; - } - - @Override - public Object get(Object key) { - Preconditions.notNull(key, "key must not be null"); - return accessStore(() -> this.valuesStore.get(this.namespace, key)); - } - - @Override - public V get(Object key, Class requiredType) { - Preconditions.notNull(key, "key must not be null"); - Preconditions.notNull(requiredType, "requiredType must not be null"); - return accessStore(() -> this.valuesStore.get(this.namespace, key, requiredType)); - } - - @Override - public Object getOrComputeIfAbsent(K key, Function defaultCreator) { - Preconditions.notNull(key, "key must not be null"); - Preconditions.notNull(defaultCreator, "defaultCreator function must not be null"); - return accessStore(() -> this.valuesStore.getOrComputeIfAbsent(this.namespace, key, defaultCreator)); - } - - @Override - public V getOrComputeIfAbsent(K key, Function defaultCreator, Class requiredType) { - Preconditions.notNull(key, "key must not be null"); - Preconditions.notNull(defaultCreator, "defaultCreator function must not be null"); - Preconditions.notNull(requiredType, "requiredType must not be null"); - return accessStore( - () -> this.valuesStore.getOrComputeIfAbsent(this.namespace, key, defaultCreator, requiredType)); - } - - @Override - public void put(Object key, Object value) { - Preconditions.notNull(key, "key must not be null"); - accessStore(() -> this.valuesStore.put(this.namespace, key, value)); - } - - @Override - public Object remove(Object key) { - Preconditions.notNull(key, "key must not be null"); - return accessStore(() -> this.valuesStore.remove(this.namespace, key)); - } - - @Override - public T remove(Object key, Class requiredType) { - Preconditions.notNull(key, "key must not be null"); - Preconditions.notNull(requiredType, "requiredType must not be null"); - return accessStore(() -> this.valuesStore.remove(this.namespace, key, requiredType)); - } - - private T accessStore(Supplier action) { - try { - return action.get(); - } - catch (NamespacedHierarchicalStoreException e) { - throw new ResourceContextException(e.getMessage(), e); - } - } - -} diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/ResourceContext.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/ResourceContext.java deleted file mode 100644 index de031b33000c..000000000000 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/ResourceContext.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2015-2024 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.store; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.function.Function; - -import org.apiguardian.api.API; -import org.junit.platform.commons.support.ReflectionSupport; -import org.junit.platform.commons.util.Preconditions; - -public interface ResourceContext { - - Optional getParent(); - - ResourceContext getRoot(); - - // TODO: Implement methods to retrieve session and request - // Optional getSession(); - // Optional getRequest(); - - // TODO : Implement methods to retrieve lifecycle about session and request - // Optional getSessionLifecycle(); - // Optional getRequestLifecycle(); - - interface Store { - - @API(status = STABLE, since = "5.1") - interface CloseableResource { - - void close() throws Throwable; - - } - - Object get(Object key); - - V get(Object key, Class requiredType); - - default V getOrDefault(Object key, Class requiredType, V defaultValue) { - V value = get(key, requiredType); - return (value != null ? value : defaultValue); - } - - @API(status = STABLE, since = "5.1") - default V getOrComputeIfAbsent(Class type) { - return getOrComputeIfAbsent(type, ReflectionSupport::newInstance, type); - } - - Object getOrComputeIfAbsent(K key, Function defaultCreator); - - V getOrComputeIfAbsent(K key, Function defaultCreator, Class requiredType); - - void put(Object key, Object value); - - Object remove(Object key); - - V remove(Object key, Class requiredType); - - } - - class Namespace { - - public static final Namespace GLOBAL = Namespace.create(new Object()); - - public static Namespace create(Object... parts) { - Preconditions.notEmpty(parts, "parts array must not be null or empty"); - Preconditions.containsNoNullElements(parts, "individual parts must not be null"); - return new Namespace(new ArrayList<>(Arrays.asList(parts))); - } - - private final List parts; - - private Namespace(List parts) { - this.parts = parts; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Namespace that = (Namespace) o; - return this.parts.equals(that.parts); - } - - @Override - public int hashCode() { - return this.parts.hashCode(); - } - - public Namespace append(Object... parts) { - Preconditions.notEmpty(parts, "parts array must not be null or empty"); - Preconditions.containsNoNullElements(parts, "individual parts must not be null"); - ArrayList newParts = new ArrayList<>(this.parts.size() + parts.length); - newParts.addAll(this.parts); - Collections.addAll(newParts, parts); - return new Namespace(newParts); - } - } - -} diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/ResourceContextException.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/ResourceContextException.java deleted file mode 100644 index 604725ef9b69..000000000000 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/ResourceContextException.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2015-2024 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.store; - -public class ResourceContextException extends RuntimeException { - public ResourceContextException(String message) { - super(message); - } - - public ResourceContextException(String message, Throwable cause) { - super(message, cause); - } - -} diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java index efab8b95458f..17d6fff045ee 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java @@ -11,9 +11,9 @@ package org.junit.platform.launcher; import static org.apiguardian.api.API.Status.STABLE; -import static org.junit.platform.engine.support.store.ResourceContext.*; import org.apiguardian.api.API; +import org.junit.platform.engine.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.core.LauncherFactory; diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java index b1f1a261a901..f090dc6d3372 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java @@ -10,12 +10,11 @@ package org.junit.platform.launcher.core; -import static org.junit.platform.engine.support.store.ResourceContext.*; - import java.util.List; import java.util.function.Supplier; import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.engine.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; From 234f7ff3209fb9f1c61d7c6e000f4de4175f5f46 Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Sat, 18 Jan 2025 20:38:09 +0900 Subject: [PATCH 04/55] Apply comment Issue: #2816 --- .../api/extension/ExtensionContext.java | 22 +++++++++++++++++++ .../descriptor/AbstractExtensionContext.java | 10 +++++++++ .../org/junit/platform/engine/Namespace.java | 2 +- .../store/NamespacedHierarchicalStore.java | 13 +++++++++++ 4 files changed, 46 insertions(+), 1 deletion(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java index 59fece5cd109..3d5ec178f015 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java @@ -418,6 +418,28 @@ default void publishReportEntry(String value) { */ Store getStore(Namespace namespace); + /** + * Returns the store for session-level data. + * + *

This store is used to store data that is scoped to the session level. + * The data stored in this store will be available throughout the entire session. + * + * @return the store for session-level data + * @since 5.13 + */ + Store getSessionLevelStore(); + + /** + * Returns the store for request-level data. + * + *

This store is used to store data that is scoped to the request level. + * The data stored in this store will be available only for the duration of the current request. + * + * @return the store for request-level data + * @since 5.13 + */ + Store getRequestLevelStore(); + /** * Get the {@link ExecutionMode} associated with the current test or container. * diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java index bf7cb5d9a059..fd9b6dae734a 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java @@ -192,6 +192,16 @@ public Store getStore(Namespace namespace) { return new NamespaceAwareStore(this.valuesStore, namespace); } + @Override + public Store getSessionLevelStore() { + return getStore(Namespace.create("session")); + } + + @Override + public Store getRequestLevelStore() { + return getStore(Namespace.create("request")); + } + @Override public Set getTags() { // return modifiable copy diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/Namespace.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/Namespace.java index b97bf19a341a..7fe2d37cb2f8 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/Namespace.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/Namespace.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * Copyright 2015-2025 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStore.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStore.java index 9167dde42b13..6b5d1af79af7 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStore.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStore.java @@ -17,6 +17,7 @@ import java.util.Comparator; import java.util.Objects; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; @@ -85,6 +86,18 @@ public NamespacedHierarchicalStore newChild() { return new NamespacedHierarchicalStore<>(this, this.closeAction); } + /** + * Returns the parent store of this {@code NamespacedHierarchicalStore}. + * + *

If this store does not have a parent, an empty {@code Optional} is returned. + * + * @return an {@code Optional} containing the parent store, or an empty {@code Optional} if there is no parent + * @since 5.13 + */ + public Optional> getParent() { + return Optional.ofNullable(this.parentStore); + } + /** * Determine if this store has been {@linkplain #close() closed}. * From ffde19cb7a87d31eb21b06a8d9ccd4fe8f393f8f Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Sun, 19 Jan 2025 19:19:20 +0900 Subject: [PATCH 05/55] Adding a request-level store to Launcher Issue: #2816 --- .../org/junit/platform/launcher/Launcher.java | 3 +++ .../platform/launcher/core/DefaultLauncher.java | 15 ++++++++++++++- .../launcher/core/DefaultLauncherSession.java | 5 +++++ .../launcher/core/DelegatingLauncher.java | 7 +++++++ .../platform/launcher/core/LauncherFactory.java | 2 +- .../launcher/core/SessionPerRequestLauncher.java | 9 +++++++++ 6 files changed, 39 insertions(+), 2 deletions(-) diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java index 3b46078278ca..6c4e05176cd8 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java @@ -13,6 +13,8 @@ import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; +import org.junit.platform.engine.Namespace; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; /** * The {@code Launcher} API is the main entry point for client code that @@ -127,4 +129,5 @@ public interface Launcher { @API(status = STABLE, since = "1.4") void execute(TestPlan testPlan, TestExecutionListener... listeners); + NamespacedHierarchicalStore getStore(LauncherDiscoveryRequest launcherDiscoveryRequest); } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java index 5ebc4f9a638e..cbdc9023896d 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java @@ -17,10 +17,13 @@ import java.util.Collection; import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.Namespace; import org.junit.platform.engine.TestEngine; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.LauncherSession; import org.junit.platform.launcher.PostDiscoveryFilter; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestPlan; @@ -41,6 +44,8 @@ class DefaultLauncher implements Launcher { private final EngineExecutionOrchestrator executionOrchestrator = new EngineExecutionOrchestrator( listenerRegistry.testExecutionListeners); private final EngineDiscoveryOrchestrator discoveryOrchestrator; + private final LauncherSession launcherSession; + private final NamespacedHierarchicalStore sessionStore; /** * Construct a new {@code DefaultLauncher} with the supplied test engines. @@ -50,7 +55,8 @@ class DefaultLauncher implements Launcher { * @param postDiscoveryFilters the additional post discovery filters for * discovery requests; never {@code null} */ - DefaultLauncher(Iterable testEngines, Collection postDiscoveryFilters) { + DefaultLauncher(Iterable testEngines, Collection postDiscoveryFilters, + LauncherConfig config) { Preconditions.condition(testEngines != null && testEngines.iterator().hasNext(), () -> "Cannot create Launcher without at least one TestEngine; " + "consider adding an engine implementation JAR to the classpath"); @@ -59,6 +65,8 @@ class DefaultLauncher implements Launcher { "PostDiscoveryFilter array must not contain null elements"); this.discoveryOrchestrator = new EngineDiscoveryOrchestrator(testEngines, unmodifiableCollection(postDiscoveryFilters), listenerRegistry.launcherDiscoveryListeners); + this.launcherSession = LauncherFactory.openSession(config); + this.sessionStore = launcherSession.getStore(); } @Override @@ -94,6 +102,11 @@ public void execute(TestPlan testPlan, TestExecutionListener... listeners) { execute((InternalTestPlan) testPlan, listeners); } + @Override + public NamespacedHierarchicalStore getStore(LauncherDiscoveryRequest launcherDiscoveryRequest) { + return new NamespacedHierarchicalStore<>(this.sessionStore); + } + private LauncherDiscoveryResult discover(LauncherDiscoveryRequest discoveryRequest, EngineDiscoveryOrchestrator.Phase phase) { return discoveryOrchestrator.discover(discoveryRequest, phase); diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java index c2b54bed66dc..09df7277989e 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java @@ -118,6 +118,11 @@ public void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, TestExecu public void execute(TestPlan testPlan, TestExecutionListener... listeners) { throw new PreconditionViolationException("Launcher session has already been closed"); } + + @Override + public NamespacedHierarchicalStore getStore(LauncherDiscoveryRequest launcherDiscoveryRequest) { + throw new PreconditionViolationException("Launcher session has already been closed"); + } } private static LauncherInterceptor composite(List interceptors) { diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncher.java index 3cc6be2316f0..297f85e02ee2 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncher.java @@ -10,6 +10,8 @@ package org.junit.platform.launcher.core; +import org.junit.platform.engine.Namespace; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; @@ -52,4 +54,9 @@ public void execute(TestPlan testPlan, TestExecutionListener... listeners) { delegate.execute(testPlan, listeners); } + @Override + public NamespacedHierarchicalStore getStore(LauncherDiscoveryRequest launcherDiscoveryRequest) { + return delegate.getStore(launcherDiscoveryRequest); + } + } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java index c756f27351f0..c6cdb93d7c2d 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java @@ -134,7 +134,7 @@ private static DefaultLauncher createDefaultLauncher(LauncherConfig config, Set engines = collectTestEngines(config); List filters = collectPostDiscoveryFilters(config); - DefaultLauncher launcher = new DefaultLauncher(engines, filters); + DefaultLauncher launcher = new DefaultLauncher(engines, filters, config); registerLauncherDiscoveryListeners(config, launcher); registerTestExecutionListeners(config, launcher, configurationParameters); diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java index dffde014867a..1aaa71af400a 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java @@ -13,6 +13,8 @@ import java.util.List; import java.util.function.Supplier; +import org.junit.platform.engine.Namespace; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; @@ -71,6 +73,13 @@ public void execute(TestPlan testPlan, TestExecutionListener... listeners) { } } + @Override + public NamespacedHierarchicalStore getStore(LauncherDiscoveryRequest launcherDiscoveryRequest) { + try (LauncherSession session = createSession()) { + return session.getLauncher().getStore(launcherDiscoveryRequest); + } + } + private LauncherSession createSession() { LauncherSession session = new DefaultLauncherSession(interceptorFactory.get(), sessionListenerSupplier, launcherSupplier); From 4fa576f7311d98043f67c4592693780292b96981 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Sun, 19 Jan 2025 16:56:25 +0100 Subject: [PATCH 06/55] Remove logging to SYS_ERR in test --- .../org/junit/platform/commons/util/ReflectionUtilsTests.java | 1 - 1 file changed, 1 deletion(-) diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java index b001ee14bd76..2a2256ddcfc1 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java @@ -1596,7 +1596,6 @@ void findMethodsWithoutStaticHidingUsingHierarchyDownMode() throws Exception { var methods = findMethods(child, method -> true, TOP_DOWN); assertEquals(9, methods.size()); - methods.forEach(System.err::println); assertThat(methods.subList(0, 2)).containsOnly(ifcMethod1, ifcMethod2); assertThat(methods.subList(2, 6)).containsOnly(parentMethod1, parentMethod2, parentMethod4, parentMethod5); assertThat(methods.subList(6, 9)).containsOnly(childMethod1, childMethod4, childMethod5); From 57e20cd46e6ca9e5387fef860e6cdb0d3b3ec675 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Sun, 19 Jan 2025 17:42:05 +0100 Subject: [PATCH 07/55] Revise Javadoc and release notes regarding void lookups See #4048 --- .../release-notes/release-notes-5.12.0-M1.adoc | 6 ++++-- .../platform/commons/util/ReflectionUtils.java | 17 +++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc index 5c15b26ba6fa..eb7d9240fb50 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc @@ -42,8 +42,10 @@ JUnit repository on GitHub. `addClassContainerSelectorResolver()`. * Introduce `ReflectionSupport.makeAccessible(Field)` for third-party use rather than calling the internal `ReflectionUtils.makeAccessible(Field)` method directly. -* Support both the primitive type `void` and the wrapper type `Void` in the internal - `ReflectionUtils` to allow `String` to `Class` conversion in parameterized tests. +* The `ReflectionSupport.tryToLoadClass(...)` utility methods now support lookups for the + `"void"` pseudo-type, which indirectly supports `String` to `Class` conversion for + `"void"` in parameterized tests in JUnit Jupiter. +* `ReflectionUtils` now treats `Void` as a _wrapper_ type for the `void` pseudo-type. * New `--exclude-methodname` and `--include-methodname` options added to the `ConsoleLauncher` to include or exclude methods based on fully qualified method names without parameters. For example, `--exclude-methodname=^org\.example\..+#methodname` diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java index 53dc2fbc213d..7b4a70d3ae27 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java @@ -418,9 +418,9 @@ public static boolean isMultidimensionalArray(Object obj) { * supplied target type for the purpose of reflective method invocations. * *

In contrast to {@link Class#isAssignableFrom(Class)}, this method - * returns {@code true} if the target type represents a primitive type whose - * wrapper matches the supplied source type. In addition, this method - * also supports + * returns {@code true} if the target type represents a primitive type (or + * {@code void}) whose wrapper matches the supplied source type. In addition, + * this method also supports * * widening conversions for primitive target types. * @@ -454,8 +454,8 @@ public static boolean isAssignableTo(Class sourceType, Class targetType) { * type for the purpose of reflective method invocations. * *

In contrast to {@link Class#isInstance(Object)}, this method returns - * {@code true} if the target type represents a primitive type whose - * wrapper matches the supplied object's type. In addition, this method + * {@code true} if the target type represents a primitive type (or {@code void}) + * whose wrapper matches the supplied object's type. In addition, this method * also supports * * widening conversions for primitive types and their corresponding @@ -550,11 +550,12 @@ static boolean isWideningConversion(Class sourceType, Class targetType) { } /** - * Get the wrapper type for the supplied primitive type. + * Get the wrapper type for the supplied primitive type (or {@code void}). * - * @param type the primitive type for which to retrieve the wrapper type + * @param type the primitive type (or {@code void}) for which to retrieve the + * wrapper type * @return the corresponding wrapper type or {@code null} if the - * supplied type is {@code null} or not a primitive type + * supplied type is {@code null} or not a primitive type or {@code void} */ public static Class getWrapperType(Class type) { return primitiveToWrapperMap.get(type); From 30cbe62814d46251e36c0fa6dfc762728dac42f1 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Sun, 19 Jan 2025 17:44:11 +0100 Subject: [PATCH 08/55] Move Kotlin contracts release note to correct section --- .../docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc index eb7d9240fb50..6900df74ec8b 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc @@ -71,7 +71,6 @@ JUnit repository on GitHub. in the XML report. * Output written to `System.out` and `System.err` from non-test threads is now attributed to the most recent test or container that was started or has written output. -* Introduced contracts for Kotlin-specific assertion methods. * New public interface `ClasspathScanner` allowing third parties to provide a custom implementation for scanning the classpath for classes and resources. @@ -102,6 +101,7 @@ JUnit repository on GitHub. [[release-notes-5.12.0-M1-junit-jupiter-new-features-and-improvements]] ==== New Features and Improvements +* Introduced contracts for Kotlin-specific assertion methods. * In a `@ParameterizedTest` method, a `null` value can now be supplied for Java Date/Time types such as `LocalDate` if the new `nullable` attribute in `@JavaTimeConversionPattern` is set to `true`. From 254ca623d60534be222e404ccaccd621ac3fa14a Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Sun, 19 Jan 2025 18:37:02 +0100 Subject: [PATCH 09/55] =?UTF-8?q?Revise=20recent=20changes=20to=20@?= =?UTF-8?q?=E2=81=A0ResourceLock=20and=20related=20APIs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jupiter/api/parallel/ResourceLock.java | 78 +++++++-------- .../api/parallel/ResourceLockTarget.java | 20 ++-- .../api/parallel/ResourceLocksProvider.java | 97 +++++++++---------- 3 files changed, 89 insertions(+), 106 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLock.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLock.java index e2d3af554630..24c85cb268c8 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLock.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLock.java @@ -21,16 +21,14 @@ import java.lang.annotation.Target; import org.apiguardian.api.API; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; /** * {@code @ResourceLock} is used to declare that the annotated test class or test * method requires access to a shared resource identified by a key. * - *

The resource key is specified via {@link #value}. In addition, - * {@link #mode} allows you to specify whether the annotated test class or test - * method requires {@link ResourceAccessMode#READ_WRITE READ_WRITE} or only + *

The resource key is specified via {@link #value}. In addition, {@link #mode} + * allows one to specify whether the annotated test class or test method requires + * {@link ResourceAccessMode#READ_WRITE READ_WRITE} or * {@link ResourceAccessMode#READ READ} access to the resource. In the former case, * execution of the annotated element will occur while no other test class or * test method that uses the shared resource is being executed. In the latter case, @@ -39,46 +37,38 @@ * other test that requires {@code READ_WRITE} access. * *

This guarantee extends to lifecycle methods of a test class or method. For - * example, if a test method is annotated with a {@code @ResourceLock} - * annotation the "lock" will be acquired before any - * {@link BeforeEach @BeforeEach} methods are executed and released after all - * {@link AfterEach @AfterEach} methods have been executed. + * example, if a test method is annotated with {@code @ResourceLock} the lock + * will be acquired before any {@link org.junit.jupiter.api.BeforeEach @BeforeEach} + * methods are executed and released after all + * {@link org.junit.jupiter.api.AfterEach @AfterEach} methods have been executed. * *

This annotation can be repeated to declare the use of multiple shared resources. * - *

Uniqueness of a shared resource is identified by both {@link #value()} and - * {@link #mode()}. Duplicated shared resources do not cause errors. + *

Uniqueness of a shared resource is determined by both the {@link #value()} + * and the {@link #mode()}. Duplicated shared resources do not cause errors. * *

Since JUnit Jupiter 5.4, this annotation is {@linkplain Inherited inherited} * within class hierarchies. * *

Since JUnit Jupiter 5.12, this annotation supports adding shared resources - * dynamically at runtime via {@link #providers}. - * - *

Resources declared "statically" using {@link #value()} and {@link #mode()} - * are combined with "dynamic" resources added via {@link #providers()}. - * For example, declaring resource "A" via {@code @ResourceLock("A")} - * and resource "B" via a provider returning {@code new Lock("B")} will result - * in two shared resources "A" and "B". + * dynamically at runtime via {@link #providers}. Resources declared "statically" + * using {@link #value()} and {@link #mode()} are combined with "dynamic" resources + * added via {@link #providers()}. For example, declaring resource "A" via + * {@code @ResourceLock("A")} and resource "B" via a provider returning + * {@code new Lock("B")} will result in two shared resources "A" and "B". * *

Since JUnit Jupiter 5.12, this annotation supports declaring "static" * shared resources for direct child nodes via the {@link #target()} - * attribute. - * - *

Using the {@link ResourceLockTarget#CHILDREN} in a class-level - * annotation has the same semantics as adding an annotation with the same - * {@link #value()} and {@link #mode()} to each test method and nested test - * class declared in this class. - * - *

This may improve parallelization when a test class declares a + * attribute. Using {@link ResourceLockTarget#CHILDREN} in a class-level annotation + * has the same semantics as adding an annotation with the same {@link #value()} + * and {@link #mode()} to each test method and nested test class declared in the + * annotated class. This may improve parallelization when a test class declares a * {@link ResourceAccessMode#READ READ} lock, but only a few methods hold - * {@link ResourceAccessMode#READ_WRITE READ_WRITE} lock. - * - *

Note that the {@code target = CHILDREN} means that - * {@link #value()} and {@link #mode()} no longer apply to a node - * declaring the annotation. However, the {@link #providers()} attribute - * remains applicable, and the target of "dynamic" shared resources - * added via implementations of {@link ResourceLocksProvider} is not changed. + * {@link ResourceAccessMode#READ_WRITE READ_WRITE} lock. Note that + * {@code target = CHILDREN} means that {@link #value()} and {@link #mode()} no + * longer apply to a node declaring the annotation. However, the {@link #providers()} + * attribute remains applicable, and the target of "dynamic" shared resources added + * via implementations of {@link ResourceLocksProvider} is not changed. * * @see Isolated * @see Resources @@ -116,28 +106,28 @@ ResourceAccessMode mode() default ResourceAccessMode.READ_WRITE; /** - * The target of a resource created from {@link #value()} and {@link #mode()}. - * - *

Defaults to {@link ResourceLockTarget#SELF SELF}. + * An array of one or more classes implementing {@link ResourceLocksProvider}. * - *

Note that using {@link ResourceLockTarget#CHILDREN} in - * a method-level annotation results in an exception. + *

Defaults to an empty array. * - * @see ResourceLockTarget + * @see ResourceLocksProvider.Lock * @since 5.12 */ @API(status = EXPERIMENTAL, since = "5.12") - ResourceLockTarget target() default ResourceLockTarget.SELF; + Class[] providers() default {}; /** - * An array of one or more classes implementing {@link ResourceLocksProvider}. + * The target of a resource created from {@link #value()} and {@link #mode()}. * - *

Defaults to an empty array. + *

Defaults to {@link ResourceLockTarget#SELF SELF}. * - * @see ResourceLocksProvider.Lock + *

Note that using {@link ResourceLockTarget#CHILDREN} in a method-level + * annotation results in an exception. + * + * @see ResourceLockTarget * @since 5.12 */ @API(status = EXPERIMENTAL, since = "5.12") - Class[] providers() default {}; + ResourceLockTarget target() default ResourceLockTarget.SELF; } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLockTarget.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLockTarget.java index 883869134d48..94a7a0bf7d68 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLockTarget.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLockTarget.java @@ -31,17 +31,17 @@ public enum ResourceLockTarget { /** * Add a shared resource to the direct children of the current node. * - *

Examples of "parent - child" relationship in the context of - * {@link ResourceLockTarget}: + *

Examples of "parent - child" relationships in the context of + * {@code ResourceLockTarget}: *

    - *
  • a test class - * - test methods and nested test classes declared in the class.
  • - *
  • a nested test class - * - test methods and nested test classes declared in the nested class. - *
  • - *
  • a test method - * - considered to have no children. Using {@code CHILDREN} for - * a test method results in an exception.
  • + *
  • test class: test methods and nested test classes + * declared in the test class are children of the test class.
  • + *
  • nested test class: test methods and nested test + * classes declared in the nested class are children of the nested test class. + *
  • + *
  • test method: test methods are not considered to have + * children. Using {@code CHILDREN} for a test method results in an + * exception.
  • *
*/ CHILDREN diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java index 6e531c478d4f..be30638bb43d 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java @@ -18,20 +18,18 @@ import java.util.Set; import org.apiguardian.api.API; -import org.junit.jupiter.api.Nested; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; /** - * {@code @ResourceLocksProvider} is used to add shared resources - * to a test class and / or its test methods dynamically at runtime. + * A {@code ResourceLocksProvider} is used to programmatically add shared resources + * to a test class or its test methods dynamically at runtime. * *

Each shared resource is represented by an instance of {@link Lock}. * - *

Adding shared resources using this interface has the same semantics - * as declaring them via {@code @ResourceLock(value, mode)} annotation - * but for some cases may be a more flexible and less verbose alternative - * since it allows to add resources programmatically. + *

Adding shared resources via this API has the same semantics as declaring + * them declaratively via {@link ResouceLock @ResourceLock(value, mode)}, but for + * some use cases the programmatic approach may be more flexible and less verbose. * *

Implementations must provide a no-args constructor. * @@ -47,14 +45,14 @@ public interface ResourceLocksProvider { /** * Add shared resources to a test class. * - *

Invoked in case a test class or its parent class - * is annotated with {@code @ResourceLock(providers)}. + *

Invoked in case a test class or its parent class is annotated with + * {@code @ResourceLock(providers)}. * - * @apiNote Adding {@linkplain Lock a shared resource} with this method - * has the same semantics as annotating a test class - * with analogous {@code @ResourceLock(value, mode)}. + * @apiNote Adding {@linkplain Lock a shared resource} via this method has + * the same semantics as annotating a test class with an analogous + * {@code @ResourceLock(value, mode)} declaration. * - * @param testClass a test class to add shared resources + * @param testClass a test class for which to add shared resources * @return a set of {@link Lock}; may be empty */ default Set provideForClass(Class testClass) { @@ -62,25 +60,22 @@ default Set provideForClass(Class testClass) { } /** - * Add shared resources to a {@linkplain Nested nested} test class. + * Add shared resources to a {@linkplain org.junit.jupiter.api.Nested @Nested} + * test class. * *

Invoked in case: *

    - *
  • an enclosing test class of any level or its parent class - * is annotated with {@code @ResourceLock(providers)}.
  • - *
  • a nested test class or its parent class - * is annotated with {@code @ResourceLock(providers)}.
  • + *
  • an enclosing test class of any level or its parent class is + * annotated with {@code @ResourceLock(providers = ...)}.
  • + *
  • a nested test class or its parent class is annotated with + * {@code @ResourceLock(providers = ...)}.
  • *
* - *

Invoked for a nested test class - * annotated with {@code @ResourceLock(providers)} - * and for its child classes. - * - * @apiNote Adding {@linkplain Lock a shared resource} with this method - * has the same semantics as annotating a nested test class - * with analogous {@code @ResourceLock(value, mode)}. + * @apiNote Adding {@linkplain Lock a shared resource} via this method has + * the same semantics as annotating a nested test class with an analogous + * {@code @ResourceLock(value, mode)} declaration. * - * @param testClass a nested test class to add shared resources + * @param testClass a nested test class for which to add shared resources * @return a set of {@link Lock}; may be empty * @see Nested */ @@ -93,19 +88,18 @@ default Set provideForNestedClass(Class testClass) { * *

Invoked in case: *

    - *
  • an enclosing test class of any level or its parent class - * is annotated with {@code @ResourceLock(providers)}.
  • - *
  • a test method - * is annotated with {@code @ResourceLock(providers)}.
  • + *
  • an enclosing test class of any level or its parent class is + * annotated with {@code @ResourceLock(providers)}.
  • + *
  • a test method is annotated with {@code @ResourceLock(providers)}.
  • *
* * @apiNote Adding {@linkplain Lock a shared resource} with this method * has the same semantics as annotating a test method * with analogous {@code @ResourceLock(value, mode)}. * - * @param testClass a test class - * or {@linkplain Nested nested} test class containing the {@code testMethod} - * @param testMethod a test method to add shared resources + * @param testClass the test class or {@code @Nested} test class that contains + * the {@code testMethod} + * @param testMethod a test method for which to add shared resources * @return a set of {@link Lock}; may be empty */ default Set provideForMethod(Class testClass, Method testMethod) { @@ -113,16 +107,14 @@ default Set provideForMethod(Class testClass, Method testMethod) { } /** + * {@code Lock} represents a shared resource. * - *

{@link Lock} represents a shared resource. - * - *

Each resource is identified by a {@link #key}. - * In addition, the {@link #accessMode} allows to specify - * whether a test class or test - * method requires {@link ResourceAccessMode#READ_WRITE READ_WRITE} - * or only {@link ResourceAccessMode#READ READ} access to the resource. + *

Each resource is identified by a {@linkplain #getKey() key}. In addition, + * the {@linkplain #getAccessMode() access mode} allows one to specify whether + * a test class or test method requires {@link ResourceAccessMode#READ_WRITE + * READ_WRITE} or {@link ResourceAccessMode#READ READ} access to the resource. * - * @apiNote {@link Lock#key} and {@link Lock#accessMode} have the same + * @apiNote {@link #getKey()} and {@link #getAccessMode()} have the same * semantics as {@link ResourceLock#value()} and {@link ResourceLock#mode()} * respectively. * @@ -140,7 +132,7 @@ final class Lock { private final ResourceAccessMode accessMode; /** - * Create a new {@code Lock} with {@code accessMode = READ_WRITE}. + * Create a new {@code Lock} with {@link ResourceAccessMode#READ_WRITE}. * * @param key the identifier of the resource; never {@code null} or blank * @see ResourceLock#value() @@ -153,7 +145,8 @@ public Lock(String key) { * Create a new {@code Lock}. * * @param key the identifier of the resource; never {@code null} or blank - * @param accessMode the lock mode to use to synchronize access to the resource; never {@code null} + * @param accessMode the lock mode to use to synchronize access to the + * resource; never {@code null} * @see ResourceLock#value() * @see ResourceLock#mode() */ @@ -163,21 +156,21 @@ public Lock(String key, ResourceAccessMode accessMode) { } /** - * Get the key of this lock. + * Get the key for this lock. * * @see ResourceLock#value() */ public String getKey() { - return key; + return this.key; } /** - * Get the access mode of this lock. + * Get the access mode for this lock. * * @see ResourceLock#mode() */ public ResourceAccessMode getAccessMode() { - return accessMode; + return this.accessMode; } @Override @@ -188,20 +181,20 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } - Lock lock = (Lock) o; - return Objects.equals(key, lock.key) && accessMode == lock.accessMode; + Lock that = (Lock) o; + return this.key.equals(that.key) && this.accessMode == that.accessMode; } @Override public int hashCode() { - return Objects.hash(key, accessMode); + return Objects.hash(this.key, this.accessMode); } @Override public String toString() { return new ToStringBuilder(this) // - .append("key", key) // - .append("accessMode", accessMode) // + .append("key", this.key) // + .append("accessMode", this.accessMode) // .toString(); } } From 2e14b3d200d79af948872156768ff966255694ec Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Sun, 19 Jan 2025 18:37:56 +0100 Subject: [PATCH 10/55] Revise release notes for 5.12 M1 --- .../release-notes-5.12.0-M1.adoc | 97 ++++++++++--------- 1 file changed, 50 insertions(+), 47 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc index 6900df74ec8b..185878c45198 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc @@ -37,24 +37,27 @@ JUnit repository on GitHub. [[release-notes-5.12.0-M1-junit-platform-new-features-and-improvements]] ==== New Features and Improvements -* New `addResourceContainerSelectorResolver()` in `EngineDiscoveryRequestResolver.Builder` to - support the discovery of class path resource based tests, analogous to the - `addClassContainerSelectorResolver()`. -* Introduce `ReflectionSupport.makeAccessible(Field)` for third-party use rather than - calling the internal `ReflectionUtils.makeAccessible(Field)` method directly. +* `ConsoleLauncher` now accepts multiple values for all `--select` options. +* `ConsoleLauncher` now supports a `--select-unique-id` option to select containers and + tests by unique ID. +* `ConsoleLauncher` supports new `--exclude-methodname` and `--include-methodname` options + to include or exclude methods based on fully qualified method names without parameters. + For example, `--exclude-methodname=^org\.example\..+#methodname` will exclude all + methods called `methodName` under package `org.example`. +* The `--select-file` and `--select-resource` options for the `ConsoleLauncher` now + support line and column numbers. +* New `ReflectionSupport.makeAccessible(Field)` public utility method to be used by third + parties instead of calling the internal `ReflectionUtils.makeAccessible(Field)` method + directly. * The `ReflectionSupport.tryToLoadClass(...)` utility methods now support lookups for the `"void"` pseudo-type, which indirectly supports `String` to `Class` conversion for `"void"` in parameterized tests in JUnit Jupiter. * `ReflectionUtils` now treats `Void` as a _wrapper_ type for the `void` pseudo-type. -* New `--exclude-methodname` and `--include-methodname` options added to the - `ConsoleLauncher` to include or exclude methods based on fully qualified method names - without parameters. For example, `--exclude-methodname=^org\.example\..+#methodname` - will exclude all methods called `methodName` under package `org.example`. -* Add support for passing line and column number to `ConsoleLauncher` via - `--select-file` and `--select-resource`. -* `ConsoleLauncher` now accepts multiple values for all `--select` options. -* Add `--select-unique-id` support to ConsoleLauncher. -* Add `getOutputDirectoryProvider()` method to `EngineDiscoveryRequest` and `TestPlan` to +* New `addResourceContainerSelectorResolver()` method in + `EngineDiscoveryRequestResolver.Builder` which supports the discovery of class path + resource based tests, analogous to the existing `addClassContainerSelectorResolver()` + method. +* New `getOutputDirectoryProvider()` method in `EngineDiscoveryRequest` and `TestPlan` to allow test engines to publish/attach files to containers and tests by calling `EngineExecutionListener.fileEntryPublished(...)`. Registered `TestExecutionListeners` can then access these files by overriding the `fileEntryPublished(...)` method. @@ -86,61 +89,61 @@ JUnit repository on GitHub. [[release-notes-5.12.0-M1-junit-jupiter-deprecations-and-breaking-changes]] ==== Deprecations and Breaking Changes -* When injecting `TestInfo` into test class constructors it now contains data of the test - method the test class instance is being created for unless the test instance lifecycle - is set to `PER_CLASS` (in which case it continues to contain the data of the test - class). If you require the `TestInfo` of the test class, you can implement a class-level - lifecycle method (e.g., `@BeforeAll`) and inject `TestInfo` into that method. +* When injecting `TestInfo` into test class constructors, the `TestInfo` now contains data + for the test method for which the test class instance is being created, unless the test + instance lifecycle is set to `PER_CLASS` (in which case it continues to contain the data + for the test class). If you require the `TestInfo` of the test class, you can implement + a `@BeforeAll` lifecycle method and inject `TestInfo` into that method. * When injecting `TestReporter` into test class constructors the published report entries - are now associated with the test method rather than the test class unless the test - instance lifecycle is set to `PER_CLASS` (in which case they will continue to be - associated with the test class). If you want to publish report entries for the test - class, you can implement a class-level lifecycle method (e.g., `@BeforeAll`) and inject + are now associated with the test method rather than the test class, unless the test + instance lifecycle is set to `PER_CLASS` (in which case the published report entries + will continue to be associated with the test class). If you want to publish report + entries for the test class, you can implement a `@BeforeAll` lifecycle method and inject `TestReporter` into that method. [[release-notes-5.12.0-M1-junit-jupiter-new-features-and-improvements]] ==== New Features and Improvements -* Introduced contracts for Kotlin-specific assertion methods. +* Kotlin contracts for Kotlin-specific assertion methods in `Assertions`. +* `@TempDir` is now supported on test class constructors. +* Shared resource locks may now be determined programmatically at runtime via the new + `@ResourceLock#providers` attribute that accepts implementations of + `ResourceLocksProvider`. +* Shared resource locks for _direct_ child nodes may now be configured via the new + `@ResourceLock(target = CHILDREN)` attribute. This may improve parallelization when + a test class declares a `READ` lock, but only a few methods hold a `READ_WRITE` lock. +* `@EnumSource` has new `from` and `to` attributes that support the selection of enum + constants within the specified range. * In a `@ParameterizedTest` method, a `null` value can now be supplied for Java Date/Time types such as `LocalDate` if the new `nullable` attribute in `@JavaTimeConversionPattern` is set to `true`. +* The new `@ParameterizedTest(allowZeroInvocations = true)` attribute allows to specify that + the absence of invocations is expected in some cases and should not cause a test failure. +* Parameterized tests now support argument count validation. If the + `junit.jupiter.params.argumentCountValidation=strict` configuration parameter or the + `@ParameterizedTest(argumentCountValidation = STRICT)` attribute is set, any mismatch + between the declared number of arguments and the number of arguments provided by the + arguments source will result in an error. By default, it is still only an error if there + are fewer arguments provided than declared. * `ArgumentsProvider` (declared via `@ArgumentsSource`), `ArgumentConverter` (declared via `@ConvertWith`), and `ArgumentsAggregator` (declared via `@AggregateWith`) implementations can now use constructor injection from registered `ParameterResolver` extensions. -* Extensions based on `TestTemplateInvocationContextProvider` can now allow returning zero - invocation contexts by overriding the new `mayReturnZeroTestTemplateInvocationContexts` - method. -* The new `@ParameterizedTest(allowZeroInvocations = true)` attribute allows to specify that - the absence of invocations is expected in some cases and should not cause a test failure. -* Allow determining "shared resources" at runtime via the new `@ResourceLock#providers` - attribute that accepts implementations of `ResourceLocksProvider`. -* Allow declaring "shared resources" for _direct_ child nodes via the new - `@ResourceLock(target = CHILDREN)` attribute. This may improve parallelization when - a test class declares a `READ` lock, but only a few methods hold a `READ_WRITE` lock. +* `TestTemplateInvocationContextProvider` extensions can now signal that they may + potentially return zero invocation contexts by overriding the new + `mayReturnZeroTestTemplateInvocationContexts()` method. * Extensions that implement `TestInstancePreConstructCallback`, `TestInstanceFactory`, `TestInstancePostProcessor`, `ParameterResolver`, or `InvocationInterceptor` may override the `getTestInstantiationExtensionContextScope()` method to enable receiving a test-scoped `ExtensionContext` in `Extension` methods called during test class instantiation. This behavior will become the default in future versions of JUnit. -* `@TempDir` is now supported on test class constructors. -* Parameterized tests now support argument count validation. - If the `junit.jupiter.params.argumentCountValidation=strict` configuration parameter - or the `@ParameterizedTest(argumentCountValidation = STRICT)` attribute is set, any - mismatch between the declared number of arguments and the number of arguments provided - by the arguments source will result in an error. By default, it's still only an error if - there are fewer arguments provided than declared. -* The new `PreInterruptCallback` extension point defines the API for `Extensions` that - wish to be called prior to invocations of `Thread#interrupt()` by the `@Timeout` - extension. +* The new `PreInterruptCallback` interface defines the API for `Extensions` that wish to + be called prior to invocations of `Thread#interrupt()` by the `@Timeout` extension. * When enabled via the `junit.jupiter.execution.timeout.threaddump.enabled` configuration parameter, an implementation of `PreInterruptCallback` is registered that writes a thread dump to `System.out` prior to interrupting a test thread due to a timeout. * `TestReporter` now allows publishing files for a test method or test class which can be used to include them in test reports, such as the Open Test Reporting format. -* New `from` and `to` attributes added to `@EnumSource` to support range selection of - enum constants. * Auto-registered extensions can now be <<../user-guide/index.adoc#extensions-registration-automatic-filtering, filtered>> using include and exclude patterns that can be specified as configuration parameters. @@ -162,6 +165,6 @@ JUnit repository on GitHub. [[release-notes-5.12.0-M1-junit-vintage-new-features-and-improvements]] ==== New Features and Improvements -* Introduced support for executing top-level test classes in parallel. Please refer to the +* Support for executing top-level test classes in parallel. Please refer to the <<../user-guide/index.adoc#migrating-from-junit4-parallel-execution, User Guide>> for more information. From d583ab0ed06c26fe5e2a9d2def6cae3848d09f74 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Sun, 19 Jan 2025 22:53:06 +0100 Subject: [PATCH 11/55] Fix Javadoc errors --- .../jupiter/api/parallel/ResourceLocksProvider.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java index be30638bb43d..d94024d99f3b 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java @@ -28,7 +28,7 @@ *

Each shared resource is represented by an instance of {@link Lock}. * *

Adding shared resources via this API has the same semantics as declaring - * them declaratively via {@link ResouceLock @ResourceLock(value, mode)}, but for + * them declaratively via {@link ResourceLock @ResourceLock(value, mode)}, but for * some use cases the programmatic approach may be more flexible and less verbose. * *

Implementations must provide a no-args constructor. @@ -77,7 +77,7 @@ default Set provideForClass(Class testClass) { * * @param testClass a nested test class for which to add shared resources * @return a set of {@link Lock}; may be empty - * @see Nested + * @see org.junit.jupiter.api.Nested @Nested */ default Set provideForNestedClass(Class testClass) { return emptySet(); @@ -97,10 +97,11 @@ default Set provideForNestedClass(Class testClass) { * has the same semantics as annotating a test method * with analogous {@code @ResourceLock(value, mode)}. * - * @param testClass the test class or {@code @Nested} test class that contains - * the {@code testMethod} + * @param testClass the test class or {@link org.junit.jupiter.api.Nested @Nested} + * test class that contains the {@code testMethod} * @param testMethod a test method for which to add shared resources * @return a set of {@link Lock}; may be empty + * @see org.junit.jupiter.api.Nested @Nested */ default Set provideForMethod(Class testClass, Method testMethod) { return emptySet(); From f6eff608283c935d35a373ca7cf1107f2771bfcd Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Mon, 20 Jan 2025 11:58:51 +0100 Subject: [PATCH 12/55] Polish Javadoc in TestTemplateInvocationContextProvider --- .../TestTemplateInvocationContextProvider.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.java index a6a79b881513..31076236ecae 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.java @@ -93,19 +93,19 @@ public interface TestTemplateInvocationContextProvider extends Extension { /** * Signal that this provider may provide zero - * {@linkplain TestTemplateInvocationContext invocation contexts} in the - * supplied {@code context}. + * {@linkplain TestTemplateInvocationContext invocation contexts} for the test + * template method represented by the supplied {@code context}. * *

If this method returns {@code false} (which is the default) and the * provider returns an empty stream from * {@link #provideTestTemplateInvocationContexts}, this will be considered - * an execution error. Override this method to ignore the absence of - * invocation contexts for this provider. + * an execution error. Override this method to return {@code true} to ignore + * the absence of invocation contexts for this provider. * * @param context the extension context for the test template method about * to be invoked; never {@code null} * @return {@code true} to allow zero contexts, {@code false} to fail - * execution in case of zero contexts. + * execution in case of zero contexts * * @since 5.12 */ From dde72a9a2df24592e8dc44452d4a7f1871339464 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Mon, 20 Jan 2025 13:01:46 +0100 Subject: [PATCH 13/55] Polishing --- .../org/junit/platform/commons/util/ReflectionUtilsTests.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java index 2a2256ddcfc1..e6411d5a3b5a 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java @@ -816,6 +816,10 @@ void tryToLoadClassTrimsClassName() { void tryToLoadClassForPrimitive() { assertThat(ReflectionUtils.tryToLoadClass(int.class.getName())).isEqualTo(success(int.class)); assertThat(ReflectionUtils.tryToLoadClass(void.class.getName())).isEqualTo(success(void.class)); + + // The following should be equivalent to the above, but just to be sure... + assertThat(ReflectionUtils.tryToLoadClass("int")).isEqualTo(success(int.class)); + assertThat(ReflectionUtils.tryToLoadClass("void")).isEqualTo(success(void.class)); } @Test From 5fd9e88d07a333f1e41360281bbdd98b6d4754df Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Mon, 20 Jan 2025 13:01:03 +0100 Subject: [PATCH 14/55] Remove Void wrapper registration in ReflectionUtils Closes #4048 --- .../docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc | 1 - .../java/org/junit/platform/commons/util/ReflectionUtils.java | 1 - .../org/junit/platform/commons/util/ReflectionUtilsTests.java | 4 +++- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc index 185878c45198..5d6ceaea5dc0 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc @@ -52,7 +52,6 @@ JUnit repository on GitHub. * The `ReflectionSupport.tryToLoadClass(...)` utility methods now support lookups for the `"void"` pseudo-type, which indirectly supports `String` to `Class` conversion for `"void"` in parameterized tests in JUnit Jupiter. -* `ReflectionUtils` now treats `Void` as a _wrapper_ type for the `void` pseudo-type. * New `addResourceContainerSelectorResolver()` method in `EngineDiscoveryRequestResolver.Builder` which supports the discovery of class path resource based tests, analogous to the existing `addClassContainerSelectorResolver()` diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java index 7b4a70d3ae27..dd4116d73938 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java @@ -263,7 +263,6 @@ public enum HierarchyTraversalMode { primitivesToWrappers.put(long.class, Long.class); primitivesToWrappers.put(float.class, Float.class); primitivesToWrappers.put(double.class, Double.class); - primitivesToWrappers.put(void.class, Void.class); primitiveToWrapperMap = Collections.unmodifiableMap(primitivesToWrappers); } diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java index e6411d5a3b5a..7195f25ab14a 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java @@ -561,7 +561,9 @@ void isAssignableTo() { // Wrappers to Primitives assertTrue(ReflectionUtils.isAssignableTo(Integer.class, int.class)); assertTrue(ReflectionUtils.isAssignableTo(Boolean.class, boolean.class)); - assertTrue(ReflectionUtils.isAssignableTo(Void.class, void.class)); + + // Void to void + assertFalse(ReflectionUtils.isAssignableTo(Void.class, void.class)); // Widening Conversions from Wrappers to Primitives assertTrue(ReflectionUtils.isAssignableTo(Integer.class, long.class)); From 3f40a7ea47dbbbee7bd2d086a6f72cd040b18679 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Mon, 20 Jan 2025 13:19:24 +0100 Subject: [PATCH 15/55] =?UTF-8?q?Update=20@=E2=81=A0since/@=E2=81=A0API=20?= =?UTF-8?q?versions=20in=20Reflection[Support|Utils]=20plus=20polishing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../commons/support/ReflectionSupport.java | 20 +++++++++---------- .../commons/util/ReflectionUtils.java | 2 +- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java index 700fcb3d53fc..5f8d2cbeffc4 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java @@ -116,7 +116,7 @@ public static Try> tryToLoadClass(String name, ClassLoader classLoader) } /** - * Tries to get the {@linkplain Resource resources} for the supplied classpath + * Try to get the {@linkplain Resource resources} for the supplied classpath * resource name. * *

The name of a classpath resource must follow the semantics @@ -130,7 +130,8 @@ public static Try> tryToLoadClass(String name, ClassLoader classLoader) * @return a successful {@code Try} containing the loaded resources or a failed * {@code Try} containing the exception if no such resources could be loaded; * never {@code null} - * @since 1.11 + * @since 1.12 + * @see #tryToGetResources(String, ClassLoader) */ @API(status = EXPERIMENTAL, since = "1.12") public static Try> tryToGetResources(String classpathResourceName) { @@ -138,7 +139,7 @@ public static Try> tryToGetResources(String classpathResourceName) } /** - * Tries to load the {@linkplain Resource resources} for the supplied classpath + * Try to load the {@linkplain Resource resources} for the supplied classpath * resource name, using the supplied {@link ClassLoader}. * *

The name of a classpath resource must follow the semantics @@ -153,7 +154,8 @@ public static Try> tryToGetResources(String classpathResourceName) * @return a successful {@code Try} containing the loaded resources or a failed * {@code Try} containing the exception if no such resources could be loaded; * never {@code null} - * @since 1.11 + * @since 1.12 + * @see #tryToGetResources(String) */ @API(status = EXPERIMENTAL, since = "1.12") public static Try> tryToGetResources(String classpathResourceName, ClassLoader classLoader) { @@ -201,7 +203,6 @@ public static List> findAllClassesInClasspathRoot(URI root, Predicate findAllResourcesInClasspathRoot(URI root, Predicate resourceFilter) { - return ReflectionUtils.findAllResourcesInClasspathRoot(root, resourceFilter); } @@ -248,7 +249,6 @@ public static Stream> streamAllClassesInClasspathRoot(URI root, Predica */ @API(status = EXPERIMENTAL, since = "1.11") public static Stream streamAllResourcesInClasspathRoot(URI root, Predicate resourceFilter) { - return ReflectionUtils.streamAllResourcesInClasspathRoot(root, resourceFilter); } @@ -296,7 +296,6 @@ public static List> findAllClassesInPackage(String basePackageName, Pre */ @API(status = EXPERIMENTAL, since = "1.11") public static List findAllResourcesInPackage(String basePackageName, Predicate resourceFilter) { - return ReflectionUtils.findAllResourcesInPackage(basePackageName, resourceFilter); } @@ -370,6 +369,7 @@ public static Stream streamAllResourcesInPackage(String basePackageNam * @see #findAllClassesInClasspathRoot(URI, Predicate, Predicate) * @see #findAllClassesInPackage(String, Predicate, Predicate) */ + @API(status = MAINTAINED, since = "1.1.1") public static List> findAllClassesInModule(String moduleName, Predicate> classFilter, Predicate classNameFilter) { @@ -394,7 +394,6 @@ public static List> findAllClassesInModule(String moduleName, Predicate */ @API(status = EXPERIMENTAL, since = "1.11") public static List findAllResourcesInModule(String moduleName, Predicate resourceFilter) { - return ReflectionUtils.findAllResourcesInModule(moduleName, resourceFilter); } @@ -441,7 +440,6 @@ public static Stream> streamAllClassesInModule(String moduleName, Predi */ @API(status = EXPERIMENTAL, since = "1.11") public static Stream streamAllResourcesInModule(String moduleName, Predicate resourceFilter) { - return ReflectionUtils.streamAllResourcesInModule(moduleName, resourceFilter); } @@ -711,10 +709,10 @@ public static Stream> streamNestedClasses(Class clazz, Predicate T makeAccessible(T executable) { return executable; } - @API(status = INTERNAL, since = "1.11") + @API(status = INTERNAL, since = "1.12") @SuppressWarnings("deprecation") // "AccessibleObject.isAccessible()" is deprecated in Java 9 public static Field makeAccessible(Field field) { if ((!isPublic(field) || !isPublic(field.getDeclaringClass()) || isFinal(field)) && !field.isAccessible()) { From 9bddac83d69633057a39bd75989f623dd0c8d6c1 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Mon, 20 Jan 2025 13:29:50 +0100 Subject: [PATCH 16/55] Update Javadoc due to changes in 5fd9e88d07 See #4048 --- .../platform/commons/util/ReflectionUtils.java | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java index 4751db08ecc9..bcecb4f096e5 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java @@ -417,9 +417,8 @@ public static boolean isMultidimensionalArray(Object obj) { * supplied target type for the purpose of reflective method invocations. * *

In contrast to {@link Class#isAssignableFrom(Class)}, this method - * returns {@code true} if the target type represents a primitive type (or - * {@code void}) whose wrapper matches the supplied source type. In addition, - * this method also supports + * returns {@code true} if the target type represents a primitive type whose + * wrapper matches the supplied source type. In addition, this method also supports * * widening conversions for primitive target types. * @@ -453,9 +452,8 @@ public static boolean isAssignableTo(Class sourceType, Class targetType) { * type for the purpose of reflective method invocations. * *

In contrast to {@link Class#isInstance(Object)}, this method returns - * {@code true} if the target type represents a primitive type (or {@code void}) - * whose wrapper matches the supplied object's type. In addition, this method - * also supports + * {@code true} if the target type represents a primitive type whose wrapper + * matches the supplied object's type. In addition, this method also supports * * widening conversions for primitive types and their corresponding * wrapper types. @@ -549,12 +547,11 @@ static boolean isWideningConversion(Class sourceType, Class targetType) { } /** - * Get the wrapper type for the supplied primitive type (or {@code void}). + * Get the wrapper type for the supplied primitive type. * - * @param type the primitive type (or {@code void}) for which to retrieve the - * wrapper type + * @param type the primitive type for which to retrieve the wrapper type * @return the corresponding wrapper type or {@code null} if the - * supplied type is {@code null} or not a primitive type or {@code void} + * supplied type is {@code null} or not a primitive type */ public static Class getWrapperType(Class type) { return primitiveToWrapperMap.get(type); From 0fe36c757530559f599bb8ee6739c54f980acd1b Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Mon, 20 Jan 2025 14:39:42 +0100 Subject: [PATCH 17/55] Increase test coverage for ReflectionUtils.tryToLoadClass() This is a prerequisite for #4250. --- .../commons/util/ReflectionUtilsTests.java | 122 +++++++++++++----- 1 file changed, 88 insertions(+), 34 deletions(-) diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java index 7195f25ab14a..fb45c4835725 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java @@ -23,7 +23,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.platform.commons.function.Try.success; import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP; import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.TOP_DOWN; import static org.junit.platform.commons.util.ReflectionUtils.findFields; @@ -804,77 +803,132 @@ void loadClass() { @Test void tryToLoadClass() { - assertThat(ReflectionUtils.tryToLoadClass(Integer.class.getName())).isEqualTo(success(Integer.class)); - assertThat(ReflectionUtils.tryToLoadClass(Void.class.getName())).isEqualTo(success(Void.class)); + assertTryToLoadClass(getClass().getName(), getClass()); + assertTryToLoadClass(Integer.class.getName(), Integer.class); + assertTryToLoadClass(Void.class.getName(), Void.class); } @Test void tryToLoadClassTrimsClassName() { - assertThat(ReflectionUtils.tryToLoadClass(" " + Integer.class.getName() + "\t"))// - .isEqualTo(success(Integer.class)); + assertTryToLoadClass(" " + Integer.class.getName() + "\t", Integer.class); } @Test - void tryToLoadClassForPrimitive() { - assertThat(ReflectionUtils.tryToLoadClass(int.class.getName())).isEqualTo(success(int.class)); - assertThat(ReflectionUtils.tryToLoadClass(void.class.getName())).isEqualTo(success(void.class)); + void tryToLoadClassForVoidPseudoPrimitiveType() { + assertTryToLoadClass("void", void.class); + } - // The following should be equivalent to the above, but just to be sure... - assertThat(ReflectionUtils.tryToLoadClass("int")).isEqualTo(success(int.class)); - assertThat(ReflectionUtils.tryToLoadClass("void")).isEqualTo(success(void.class)); + @Test + void tryToLoadClassForPrimitiveType() { + assertTryToLoadClass("boolean", boolean.class); + assertTryToLoadClass("char", char.class); + assertTryToLoadClass("byte", byte.class); + assertTryToLoadClass("short", short.class); + assertTryToLoadClass("int", int.class); + assertTryToLoadClass("long", long.class); + assertTryToLoadClass("float", float.class); + assertTryToLoadClass("double", double.class); } @Test - void tryToLoadClassForPrimitiveArray() { - assertThat(ReflectionUtils.tryToLoadClass(int[].class.getName())).isEqualTo(success(int[].class)); + void tryToLoadClassForBinaryPrimitiveArrayName() { + assertTryToLoadClass("[Z", boolean[].class); + assertTryToLoadClass("[C", char[].class); + assertTryToLoadClass("[B", byte[].class); + assertTryToLoadClass("[S", short[].class); + assertTryToLoadClass("[I", int[].class); + assertTryToLoadClass("[J", long[].class); + assertTryToLoadClass("[F", float[].class); + assertTryToLoadClass("[D", double[].class); } @Test - void tryToLoadClassForPrimitiveArrayUsingSourceCodeSyntax() { - assertThat(ReflectionUtils.tryToLoadClass("int[]")).isEqualTo(success(int[].class)); + void tryToLoadClassForCanonicalPrimitiveArrayName() { + assertTryToLoadClass("boolean[]", boolean[].class); + assertTryToLoadClass("char[]", char[].class); + assertTryToLoadClass("byte[]", byte[].class); + assertTryToLoadClass("short[]", short[].class); + assertTryToLoadClass("int[]", int[].class); + assertTryToLoadClass("long[]", long[].class); + assertTryToLoadClass("float[]", float[].class); + assertTryToLoadClass("double[]", double[].class); } @Test - void tryToLoadClassForObjectArray() { - assertThat(ReflectionUtils.tryToLoadClass(String[].class.getName())).isEqualTo(success(String[].class)); + void tryToLoadClassForBinaryObjectArrayName() { + assertTryToLoadClass(String[].class.getName(), String[].class); } @Test - void tryToLoadClassForObjectArrayUsingSourceCodeSyntax() { - assertThat(ReflectionUtils.tryToLoadClass("java.lang.String[]")).isEqualTo(success(String[].class)); + void tryToLoadClassForCanonicalObjectArrayName() { + assertTryToLoadClass("java.lang.String[]", String[].class); } @Test - void tryToLoadClassForTwoDimensionalPrimitiveArray() { - assertThat(ReflectionUtils.tryToLoadClass(int[][].class.getName())).isEqualTo(success(int[][].class)); + void tryToLoadClassForBinaryTwoDimensionalPrimitiveArrayName() { + assertTryToLoadClass("[[Z", boolean[][].class); + assertTryToLoadClass("[[C", char[][].class); + assertTryToLoadClass("[[B", byte[][].class); + assertTryToLoadClass("[[S", short[][].class); + assertTryToLoadClass("[[I", int[][].class); + assertTryToLoadClass("[[J", long[][].class); + assertTryToLoadClass("[[F", float[][].class); + assertTryToLoadClass("[[D", double[][].class); } @Test - void tryToLoadClassForTwoDimensionaldimensionalPrimitiveArrayUsingSourceCodeSyntax() { - assertThat(ReflectionUtils.tryToLoadClass("int[][]")).isEqualTo(success(int[][].class)); + void tryToLoadClassForCanonicalTwoDimensionalPrimitiveArrayName() { + assertTryToLoadClass("boolean[][]", boolean[][].class); + assertTryToLoadClass("char[][]", char[][].class); + assertTryToLoadClass("byte[][]", byte[][].class); + assertTryToLoadClass("short[][]", short[][].class); + assertTryToLoadClass("int[][]", int[][].class); + assertTryToLoadClass("long[][]", long[][].class); + assertTryToLoadClass("float[][]", float[][].class); + assertTryToLoadClass("double[][]", double[][].class); } @Test - void tryToLoadClassForMultidimensionalPrimitiveArray() { - assertThat(ReflectionUtils.tryToLoadClass(int[][][][][].class.getName()))// - .isEqualTo(success(int[][][][][].class)); + void tryToLoadClassForBinaryMultidimensionalPrimitiveArrayName() { + assertTryToLoadClass("[[[[[Z", boolean[][][][][].class); + assertTryToLoadClass("[[[[[C", char[][][][][].class); + assertTryToLoadClass("[[[[[B", byte[][][][][].class); + assertTryToLoadClass("[[[[[S", short[][][][][].class); + assertTryToLoadClass("[[[[[I", int[][][][][].class); + assertTryToLoadClass("[[[[[J", long[][][][][].class); + assertTryToLoadClass("[[[[[F", float[][][][][].class); + assertTryToLoadClass("[[[[[D", double[][][][][].class); } @Test - void tryToLoadClassForMultidimensionalPrimitiveArrayUsingSourceCodeSyntax() { - assertThat(ReflectionUtils.tryToLoadClass("int[][][][][]")).isEqualTo(success(int[][][][][].class)); + void tryToLoadClassForCanonicalMultidimensionalPrimitiveArrayName() { + assertTryToLoadClass("boolean[][][][][]", boolean[][][][][].class); + assertTryToLoadClass("char[][][][][]", char[][][][][].class); + assertTryToLoadClass("byte[][][][][]", byte[][][][][].class); + assertTryToLoadClass("short[][][][][]", short[][][][][].class); + assertTryToLoadClass("int[][][][][]", int[][][][][].class); + assertTryToLoadClass("long[][][][][]", long[][][][][].class); + assertTryToLoadClass("float[][][][][]", float[][][][][].class); + assertTryToLoadClass("double[][][][][]", double[][][][][].class); } @Test - void tryToLoadClassForMultidimensionalObjectArray() { - assertThat(ReflectionUtils.tryToLoadClass(String[][][][][].class.getName()))// - .isEqualTo(success(String[][][][][].class)); + void tryToLoadClassForBinaryMultidimensionalObjectArrayName() { + assertTryToLoadClass(String[][][][][].class.getName(), String[][][][][].class); } @Test - void tryToLoadClassForMultidimensionalObjectArrayUsingSourceCodeSyntax() { - assertThat(ReflectionUtils.tryToLoadClass("java.lang.String[][][][][]"))// - .isEqualTo(success(String[][][][][].class)); + void tryToLoadClassForCanonicalMultidimensionalObjectArrayName() { + assertTryToLoadClass("java.lang.String[][][][][]", String[][][][][].class); + } + + private static void assertTryToLoadClass(String name, Class type) { + try { + assertThat(ReflectionUtils.tryToLoadClass(name).get()).as(name).isEqualTo(type); + } + catch (Exception ex) { + ExceptionUtils.throwAsUncheckedException(ex); + } } } From 2bae68b1861285b8de6dffabbdf860eaa01a9b6f Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Fri, 17 Jan 2025 19:04:53 +0100 Subject: [PATCH 18/55] Delete cases covered by `Class.forName()` `Class.forName()` from `java.base/java.lang` has already built-in support for type names matching: - _Primitive arrays such as "[I", "[[[[D", etc._ and - _Object arrays such as "[Ljava.lang.String;", etc._ This commit deletes the redundant implementation from JUnit's internal `ReflectionUtils` helper. --- .../commons/util/ReflectionUtils.java | 41 +------------------ 1 file changed, 1 insertion(+), 40 deletions(-) diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java index bcecb4f096e5..d0c9921d92e3 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java @@ -129,21 +129,6 @@ public enum HierarchyTraversalMode { BOTTOM_UP } - // Pattern: "[Ljava.lang.String;", "[[[[Ljava.lang.String;", etc. - private static final Pattern VM_INTERNAL_OBJECT_ARRAY_PATTERN = Pattern.compile("^(\\[+)L(.+);$"); - - /** - * Pattern: "[x", "[[[[x", etc., where x is Z, B, C, D, F, I, J, S, etc. - * - *

The pattern intentionally captures the last bracket with the - * capital letter so that the combination can be looked up via - * {@link #classNameToTypeMap}. For example, the last matched group - * will contain {@code "[I"} instead of {@code "I"}. - * - * @see Class#getName() - */ - private static final Pattern VM_INTERNAL_PRIMITIVE_ARRAY_PATTERN = Pattern.compile("^(\\[+)(\\[[ZBCDFIJS])$"); - // Pattern: "java.lang.String[]", "int[]", "int[][][][]", etc. // ?> => non-capturing atomic group // ++ => possessive quantifier @@ -855,32 +840,8 @@ public static Try> tryToLoadClass(String name, ClassLoader classLoader) } return Try.call(() -> { - Matcher matcher; - - // Primitive arrays such as "[I", "[[[[D", etc. - matcher = VM_INTERNAL_PRIMITIVE_ARRAY_PATTERN.matcher(trimmedName); - if (matcher.matches()) { - String brackets = matcher.group(1); - String componentTypeName = matcher.group(2); - // Calculate dimensions by counting brackets. - int dimensions = brackets.length(); - - return loadArrayType(classLoader, componentTypeName, dimensions); - } - - // Object arrays such as "[Ljava.lang.String;", "[[[[Ljava.lang.String;", etc. - matcher = VM_INTERNAL_OBJECT_ARRAY_PATTERN.matcher(trimmedName); - if (matcher.matches()) { - String brackets = matcher.group(1); - String componentTypeName = matcher.group(2); - // Calculate dimensions by counting brackets. - int dimensions = brackets.length(); - - return loadArrayType(classLoader, componentTypeName, dimensions); - } - // Arrays such as "java.lang.String[]", "int[]", "int[][][][]", etc. - matcher = SOURCE_CODE_SYNTAX_ARRAY_PATTERN.matcher(trimmedName); + Matcher matcher = SOURCE_CODE_SYNTAX_ARRAY_PATTERN.matcher(trimmedName); if (matcher.matches()) { String componentTypeName = matcher.group(1); String bracketPairs = matcher.group(2); From 5ca0ced086abc1efe4c7d2c8aeeb40e09ada2aaa Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Tue, 21 Jan 2025 12:23:00 +0100 Subject: [PATCH 19/55] Fix grammar in Javadoc for NestedMethodSelector --- .../engine/discovery/NestedMethodSelector.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedMethodSelector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedMethodSelector.java index e359547a0e30..25f17d67ca92 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedMethodSelector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedMethodSelector.java @@ -29,15 +29,14 @@ import org.junit.platform.engine.DiscoverySelectorIdentifier; /** - * A {@link DiscoverySelector} that selects a nested {@link Method} - * or a combination of enclosing classes names, class name, method - * name, and parameter types so that - * {@link org.junit.platform.engine.TestEngine TestEngines} can discover - * tests or containers based on methods. + * A {@link DiscoverySelector} that selects a nested {@link Method} or a + * combination of enclosing class names, class name, method name, and parameter + * types so that {@link org.junit.platform.engine.TestEngine TestEngines} can + * discover tests or containers based on methods. * *

If a Java {@link Method} is provided, the selector will return that * {@linkplain #getMethod() method} and its method name, class name, enclosing - * classes names, and parameter types accordingly. If class names or method names + * class names, and parameter types accordingly. If class names or method names * are provided, this selector will only attempt to lazily load a class or method * if {@link #getEnclosingClasses()}, {@link #getNestedClass()}, * {@link #getMethod()}, or {@link #getParameterTypes()} is invoked. From aec4ad9b95650390da02a3a3a30408fdb5ea2b1f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 22 Jan 2025 01:11:18 +0000 Subject: [PATCH 20/55] Update plugin commonCustomUserData to v2.1 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4f3aea0ac55d..a5458248e85d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -90,7 +90,7 @@ asciidoctorConvert = { id = "org.asciidoctor.jvm.convert", version.ref = "asciid asciidoctorPdf = { id = "org.asciidoctor.jvm.pdf", version.ref = "asciidoctor-plugins" } bnd = { id = "biz.aQute.bnd", version.ref = "bnd" } buildParameters = { id = "org.gradlex.build-parameters", version = "1.4.4" } -commonCustomUserData = { id = "com.gradle.common-custom-user-data-gradle-plugin", version = "2.0.2" } +commonCustomUserData = { id = "com.gradle.common-custom-user-data-gradle-plugin", version = "2.1" } develocity = { id = "com.gradle.develocity", version = "3.19" } foojayResolver = { id = "org.gradle.toolchains.foojay-resolver", version = "0.9.0" } gitPublish = { id = "org.ajoberstar.git-publish", version = "5.1.0" } From ba3a4ff895b08f28b3d3157f6ea8ff1877bcf8b6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 22 Jan 2025 12:44:43 +0000 Subject: [PATCH 21/55] Update actions/stale digest to 5bef64f (#4253) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/close-inactive-issues.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/close-inactive-issues.yml b/.github/workflows/close-inactive-issues.yml index 5952a1e8c3aa..a443402a9720 100644 --- a/.github/workflows/close-inactive-issues.yml +++ b/.github/workflows/close-inactive-issues.yml @@ -11,7 +11,7 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9 + - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9 with: only-labels: "status: waiting-for-feedback" days-before-stale: 14 From 10eeeda7c9e1852a1ed5bca0fedd7c81da7f8e4d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 22 Jan 2025 17:20:46 +0000 Subject: [PATCH 22/55] Update github/codeql-action digest to d68b2d4 (#4255) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 4 ++-- .github/workflows/ossf-scorecard.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index e5050471788c..3422dfa64945 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -34,7 +34,7 @@ jobs: - name: Check out repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Initialize CodeQL - uses: github/codeql-action/init@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3 + uses: github/codeql-action/init@d68b2d4edb4189fd2a5366ac14e72027bd4b37dd # v3 with: languages: ${{ matrix.language }} tools: linked @@ -47,4 +47,4 @@ jobs: -Dscan.tag.CodeQL \ allMainClasses - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3 + uses: github/codeql-action/analyze@d68b2d4edb4189fd2a5366ac14e72027bd4b37dd # v3 diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 83eb112f1b21..dca435703147 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -57,6 +57,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3 + uses: github/codeql-action/upload-sarif@d68b2d4edb4189fd2a5366ac14e72027bd4b37dd # v3 with: sarif_file: results.sarif From ab41b23d747c2d9a09cbb321c783fe17734139fb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 22 Jan 2025 22:15:15 +0000 Subject: [PATCH 23/55] Update codecov/codecov-action digest to 5a605bd (#4257) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 84861342054a..a93ed4327422 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -39,7 +39,7 @@ jobs: jacocoRootReport \ --no-configuration-cache # Disable configuration cache due to https://github.com/diffplug/spotless/issues/2318 - name: Upload to Codecov.io - uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5 + uses: codecov/codecov-action@5a605bd92782ce0810fa3b8acc235c921b497052 # v5 with: token: ${{ secrets.CODECOV_TOKEN }} From 15539b0594ed72e4961ffe22ad9e381e0f6857ef Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 00:26:19 +0000 Subject: [PATCH 24/55] Update graalvm/setup-graalvm digest to aafbedb (#4254) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a93ed4327422..1388a8e7979c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,7 +23,7 @@ jobs: with: fetch-depth: 1 - name: Install GraalVM - uses: graalvm/setup-graalvm@c09e29bb115a83bd4b7c7e99bb46e2e8a1c50466 # v1 + uses: graalvm/setup-graalvm@aafbedb8d382ed0ca6167d3a051415f20c859274 # v1 with: distribution: graalvm-community version: 'latest' From 5cb4571846906f835e5cef7a3eb3cf5edfcc1084 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 05:24:48 +0000 Subject: [PATCH 25/55] Update actions/attest-build-provenance action to v2.2.0 (#4258) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/main.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1388a8e7979c..d4060075cdc7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -91,7 +91,7 @@ jobs: publish -x check \ prepareGitHubAttestation - name: Generate build provenance attestations - uses: actions/attest-build-provenance@7668571508540a607bdfd90a87a560489fe372eb # v2.1.0 + uses: actions/attest-build-provenance@520d128f165991a6c774bcb264f323e3d70747f4 # v2.2.0 with: subject-path: documentation/build/attestation/*.jar diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 873723ee9d34..c1dad08c57a5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -51,7 +51,7 @@ jobs: :verifyArtifactsInStagingRepositoryAreReproducible \ --remote-repo-url=${{ env.STAGING_REPO_URL }} - name: Generate build provenance attestations - uses: actions/attest-build-provenance@7668571508540a607bdfd90a87a560489fe372eb # v2.1.0 + uses: actions/attest-build-provenance@520d128f165991a6c774bcb264f323e3d70747f4 # v2.2.0 with: subject-path: build/repo/**/*.jar - name: Upload local repository for later jobs From 21bb094971d0d3a266bec69e9a516c1906b8ea62 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 10:11:05 +0000 Subject: [PATCH 26/55] Update github/codeql-action digest to dd196fa (#4260) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 4 ++-- .github/workflows/ossf-scorecard.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 3422dfa64945..a39b266456ff 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -34,7 +34,7 @@ jobs: - name: Check out repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Initialize CodeQL - uses: github/codeql-action/init@d68b2d4edb4189fd2a5366ac14e72027bd4b37dd # v3 + uses: github/codeql-action/init@dd196fa9ce80b6bacc74ca1c32bd5b0ba22efca7 # v3 with: languages: ${{ matrix.language }} tools: linked @@ -47,4 +47,4 @@ jobs: -Dscan.tag.CodeQL \ allMainClasses - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@d68b2d4edb4189fd2a5366ac14e72027bd4b37dd # v3 + uses: github/codeql-action/analyze@dd196fa9ce80b6bacc74ca1c32bd5b0ba22efca7 # v3 diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index dca435703147..e9a4100dac19 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -57,6 +57,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@d68b2d4edb4189fd2a5366ac14e72027bd4b37dd # v3 + uses: github/codeql-action/upload-sarif@dd196fa9ce80b6bacc74ca1c32bd5b0ba22efca7 # v3 with: sarif_file: results.sarif From 544c3b2eb483ecc350784f853d13ddfc1193bff3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 10:11:36 +0000 Subject: [PATCH 27/55] Update plugin plantuml to v8.12 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a5458248e85d..7a4286a5bda5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -96,7 +96,7 @@ foojayResolver = { id = "org.gradle.toolchains.foojay-resolver", version = "0.9. gitPublish = { id = "org.ajoberstar.git-publish", version = "5.1.0" } jmh = { id = "me.champeau.jmh", version = "0.7.2" } nexusPublish = { id = "io.github.gradle-nexus.publish-plugin", version = "2.0.0" } -plantuml = { id = "io.freefair.plantuml", version = "8.11" } +plantuml = { id = "io.freefair.plantuml", version = "8.12" } shadow = { id = "com.gradleup.shadow", version = "8.3.5" } spotless = { id = "com.diffplug.spotless", version = "6.25.0" } versions = { id = "com.github.ben-manes.versions", version = "0.51.0" } From 3583e013109051d4965d6756c24966515da06607 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 23 Jan 2025 11:29:19 +0100 Subject: [PATCH 28/55] Use separate local Maven repos when file system type is NTFS (#4259) Prior to this commit, Maven builds triggered from integration tests sometimes failed because another concurrent build was writing to the same file in the temporary local Maven repo. --- .../support/tests/JavaVersionsTests.java | 2 +- .../tooling/support/tests/LocalMavenRepo.java | 33 ++++++++++++++++ ...obalResource.java => ManagedResource.java} | 39 ++++++++++++++++--- .../support/tests/MavenStarterTests.java | 4 +- .../MavenSurefireCompatibilityTests.java | 2 +- .../support/tests/MultiReleaseJarTests.java | 4 +- .../tests/UnalignedClasspathTests.java | 4 +- .../tests/VintageMavenIntegrationTests.java | 2 +- 8 files changed, 76 insertions(+), 14 deletions(-) rename platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/{GlobalResource.java => ManagedResource.java} (75%) diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JavaVersionsTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JavaVersionsTests.java index 4a7cce72c99e..417bb8119906 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JavaVersionsTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JavaVersionsTests.java @@ -34,7 +34,7 @@ */ class JavaVersionsTests { - @GlobalResource + @ManagedResource LocalMavenRepo localMavenRepo; @TempDir diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/LocalMavenRepo.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/LocalMavenRepo.java index dc9b99e58a8d..cb3eb217fa75 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/LocalMavenRepo.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/LocalMavenRepo.java @@ -10,14 +10,47 @@ package platform.tooling.support.tests; +import static platform.tooling.support.tests.ManagedResource.Scope.GLOBAL; +import static platform.tooling.support.tests.ManagedResource.Scope.PER_CONTEXT; + import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Comparator; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; + +@ManagedResource.Scoped(LocalMavenRepo.ScopeProvider.class) public class LocalMavenRepo implements AutoCloseable { + public static class ScopeProvider implements ManagedResource.Scoped.Provider { + + private static final Namespace NAMESPACE = Namespace.create(LocalMavenRepo.class); + + @Override + public ManagedResource.Scope determineScope(ExtensionContext extensionContext) { + var store = extensionContext.getRoot().getStore(NAMESPACE); + var fileSystemType = store.getOrComputeIfAbsent("tempFileSystemType", key -> { + var type = getFileSystemType(Path.of(System.getProperty("java.io.tmpdir"))); + extensionContext.getRoot().publishReportEntry("tempFileSystemType", type); + return type; + }, String.class); + // Writing to the same file from multiple Maven processes may fail the build on Windows + return "NTFS".equalsIgnoreCase(fileSystemType) ? PER_CONTEXT : GLOBAL; + } + + private static String getFileSystemType(Path path) { + try { + return Files.getFileStore(path).type(); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + } + } + private final Path tempDir; public LocalMavenRepo() { diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GlobalResource.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ManagedResource.java similarity index 75% rename from platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GlobalResource.java rename to platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ManagedResource.java index 8774adbda241..c278f0a8c0e8 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GlobalResource.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ManagedResource.java @@ -32,15 +32,30 @@ @Target({ ElementType.PARAMETER, ElementType.FIELD }) @Retention(RUNTIME) -@ExtendWith(GlobalResource.Extension.class) -public @interface GlobalResource { +@ExtendWith(ManagedResource.Extension.class) +public @interface ManagedResource { + + @Target(ElementType.TYPE) + @Retention(RUNTIME) + @interface Scoped { + + Class value(); + + interface Provider { + Scope determineScope(ExtensionContext extensionContext); + } + } + + enum Scope { + GLOBAL, PER_CONTEXT + } class Extension implements ParameterResolver, TestInstancePostProcessor { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { - return parameterContext.isAnnotated(GlobalResource.class); + return parameterContext.isAnnotated(ManagedResource.class); } @Override @@ -50,9 +65,14 @@ public Object resolveParameter(ParameterContext parameterContext, ExtensionConte return getOrCreateResource(extensionContext, type).get(); } + @Override + public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { + return ExtensionContextScope.TEST_METHOD; + } + @Override public void postProcessTestInstance(Object testInstance, ExtensionContext extensionContext) { - streamFields(testInstance.getClass(), field -> AnnotationSupport.isAnnotated(field, GlobalResource.class), + streamFields(testInstance.getClass(), field -> AnnotationSupport.isAnnotated(field, ManagedResource.class), HierarchyTraversalMode.BOTTOM_UP) // .forEach(field -> { try { @@ -66,7 +86,16 @@ public void postProcessTestInstance(Object testInstance, ExtensionContext extens @SuppressWarnings("unchecked") private Resource getOrCreateResource(ExtensionContext extensionContext, Class type) { - return extensionContext.getRoot().getStore(Namespace.GLOBAL) // + var scope = AnnotationSupport.findAnnotation(type, Scoped.class) // + .map(Scoped::value) // + .map(ReflectionSupport::newInstance) // + .map(provider -> provider.determineScope(extensionContext)) // + .orElse(Scope.GLOBAL); + var storingContext = switch (scope) { + case GLOBAL -> extensionContext.getRoot(); + case PER_CONTEXT -> extensionContext; + }; + return storingContext.getStore(Namespace.GLOBAL) // .getOrComputeIfAbsent(type, Resource::new, Resource.class); } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java index 9faf699942ea..5a795087abae 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java @@ -32,10 +32,10 @@ */ class MavenStarterTests { - @GlobalResource + @ManagedResource LocalMavenRepo localMavenRepo; - @GlobalResource + @ManagedResource MavenRepoProxy mavenRepoProxy; @Test diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenSurefireCompatibilityTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenSurefireCompatibilityTests.java index a59d90ecbf53..3a62853d52aa 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenSurefireCompatibilityTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenSurefireCompatibilityTests.java @@ -33,7 +33,7 @@ */ class MavenSurefireCompatibilityTests { - @GlobalResource + @ManagedResource LocalMavenRepo localMavenRepo; @ParameterizedTest diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MultiReleaseJarTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MultiReleaseJarTests.java index c07954ac5c5e..4326fa6fa7c9 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MultiReleaseJarTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MultiReleaseJarTests.java @@ -33,10 +33,10 @@ */ class MultiReleaseJarTests { - @GlobalResource + @ManagedResource LocalMavenRepo localMavenRepo; - @GlobalResource + @ManagedResource MavenRepoProxy mavenRepoProxy; @Test diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/UnalignedClasspathTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/UnalignedClasspathTests.java index a6467c511564..7869a3d59186 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/UnalignedClasspathTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/UnalignedClasspathTests.java @@ -37,10 +37,10 @@ */ class UnalignedClasspathTests { - @GlobalResource + @ManagedResource LocalMavenRepo localMavenRepo; - @GlobalResource + @ManagedResource MavenRepoProxy mavenRepoProxy; @ParameterizedTest diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageMavenIntegrationTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageMavenIntegrationTests.java index 7f735753f009..2b95c5b891d5 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageMavenIntegrationTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageMavenIntegrationTests.java @@ -29,7 +29,7 @@ class VintageMavenIntegrationTests { - @GlobalResource + @ManagedResource LocalMavenRepo localMavenRepo; @TempDir From 292f6a09fef591b3cb945cb7fcd8f7b48000f80b Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 23 Jan 2025 11:42:01 +0100 Subject: [PATCH 29/55] Revert "Use Gradle's new daemon JVM criteria feature" This causes problems on IntelliJ IDEA's 2025.1 EAP version which picks the IDE's own JetBrains JDK rather than one specified using the `JDK21` env var. Since JetBrains' JDK does not contain tools like `jar` this causes builds to break when the Gradle daemon is started via IntelliJ IDEA. This reverts commit 1e29d8e623eb019ae1463637f42c23aae2ff8765. --- gradle/gradle-daemon-jvm.properties | 2 -- gradle/plugins/settings.gradle.kts | 6 ++++++ 2 files changed, 6 insertions(+), 2 deletions(-) delete mode 100644 gradle/gradle-daemon-jvm.properties diff --git a/gradle/gradle-daemon-jvm.properties b/gradle/gradle-daemon-jvm.properties deleted file mode 100644 index 63e5bbdf4845..000000000000 --- a/gradle/gradle-daemon-jvm.properties +++ /dev/null @@ -1,2 +0,0 @@ -#This file is generated by updateDaemonJvm -toolchainVersion=21 diff --git a/gradle/plugins/settings.gradle.kts b/gradle/plugins/settings.gradle.kts index 41935db0aa36..163866db2805 100644 --- a/gradle/plugins/settings.gradle.kts +++ b/gradle/plugins/settings.gradle.kts @@ -1,3 +1,9 @@ +val expectedJavaVersion = JavaVersion.VERSION_21 +val actualJavaVersion = JavaVersion.current() +require(actualJavaVersion == expectedJavaVersion) { + "The JUnit 5 build must be executed with Java ${expectedJavaVersion.majorVersion}. Currently executing with Java ${actualJavaVersion.majorVersion}." +} + dependencyResolutionManagement { versionCatalogs { create("libs") { From 9d45d42b18ab9218244e38e945ea343c0a24169c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 12:14:46 +0100 Subject: [PATCH 30/55] Update plugin versions to v0.52.0 (#4262) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7a4286a5bda5..138ca6578ec6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -99,4 +99,4 @@ nexusPublish = { id = "io.github.gradle-nexus.publish-plugin", version = "2.0.0" plantuml = { id = "io.freefair.plantuml", version = "8.12" } shadow = { id = "com.gradleup.shadow", version = "8.3.5" } spotless = { id = "com.diffplug.spotless", version = "6.25.0" } -versions = { id = "com.github.ben-manes.versions", version = "0.51.0" } +versions = { id = "com.github.ben-manes.versions", version = "0.52.0" } From e78ca1d999ba8017a4a445a030bbc20b9c579c6a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 12:55:08 +0000 Subject: [PATCH 31/55] Update plugin develocity to v3.19.1 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 138ca6578ec6..44c5fa83a9d9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -91,7 +91,7 @@ asciidoctorPdf = { id = "org.asciidoctor.jvm.pdf", version.ref = "asciidoctor-pl bnd = { id = "biz.aQute.bnd", version.ref = "bnd" } buildParameters = { id = "org.gradlex.build-parameters", version = "1.4.4" } commonCustomUserData = { id = "com.gradle.common-custom-user-data-gradle-plugin", version = "2.1" } -develocity = { id = "com.gradle.develocity", version = "3.19" } +develocity = { id = "com.gradle.develocity", version = "3.19.1" } foojayResolver = { id = "org.gradle.toolchains.foojay-resolver", version = "0.9.0" } gitPublish = { id = "org.ajoberstar.git-publish", version = "5.1.0" } jmh = { id = "me.champeau.jmh", version = "0.7.2" } From 73d7f125992630dba1c13debf4672164a4b6cf56 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 17:57:05 +0000 Subject: [PATCH 32/55] Update github/codeql-action digest to ee117c9 --- .github/workflows/codeql-analysis.yml | 4 ++-- .github/workflows/ossf-scorecard.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index a39b266456ff..0d4067fe42a2 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -34,7 +34,7 @@ jobs: - name: Check out repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Initialize CodeQL - uses: github/codeql-action/init@dd196fa9ce80b6bacc74ca1c32bd5b0ba22efca7 # v3 + uses: github/codeql-action/init@ee117c905ab18f32fa0f66c2fe40ecc8013f3e04 # v3 with: languages: ${{ matrix.language }} tools: linked @@ -47,4 +47,4 @@ jobs: -Dscan.tag.CodeQL \ allMainClasses - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@dd196fa9ce80b6bacc74ca1c32bd5b0ba22efca7 # v3 + uses: github/codeql-action/analyze@ee117c905ab18f32fa0f66c2fe40ecc8013f3e04 # v3 diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index e9a4100dac19..bcd9ff22a8a2 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -57,6 +57,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@dd196fa9ce80b6bacc74ca1c32bd5b0ba22efca7 # v3 + uses: github/codeql-action/upload-sarif@ee117c905ab18f32fa0f66c2fe40ecc8013f3e04 # v3 with: sarif_file: results.sarif From bf08ea2ad4ab89791eac91c49a4a2197af9b1f94 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 24 Jan 2025 09:37:41 +0000 Subject: [PATCH 33/55] Update codecov/codecov-action digest to 0da7aa6 --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d4060075cdc7..3a2e2893437a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -39,7 +39,7 @@ jobs: jacocoRootReport \ --no-configuration-cache # Disable configuration cache due to https://github.com/diffplug/spotless/issues/2318 - name: Upload to Codecov.io - uses: codecov/codecov-action@5a605bd92782ce0810fa3b8acc235c921b497052 # v5 + uses: codecov/codecov-action@0da7aa657d958d32c117fc47e1f977e7524753c7 # v5 with: token: ${{ secrets.CODECOV_TOKEN }} From 6d260dadaa8416305fb2d80104827e916bc1f617 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 24 Jan 2025 13:28:12 +0100 Subject: [PATCH 34/55] Remove Twitter/X --- RELEASING.md | 1 - 1 file changed, 1 deletion(-) diff --git a/RELEASING.md b/RELEASING.md index e22831147713..641960cd0f8f 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -21,7 +21,6 @@ - [ ] Post about the new release: - [ ] [Mastodon](https://fosstodon.org/@junit) - [ ] [Bluesky](https://bsky.app/profile/junit.org) - - [ ] [Twitter/X](https://x.com/junitteam) ### Preview releases (milestones and release candidates) From a1a53537e329dd60da32eebbbbedc4b88b497fd7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 26 Jan 2025 08:30:54 +0000 Subject: [PATCH 35/55] Update codecov/codecov-action digest to 13ce06b --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3a2e2893437a..97645a94623b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -39,7 +39,7 @@ jobs: jacocoRootReport \ --no-configuration-cache # Disable configuration cache due to https://github.com/diffplug/spotless/issues/2318 - name: Upload to Codecov.io - uses: codecov/codecov-action@0da7aa657d958d32c117fc47e1f977e7524753c7 # v5 + uses: codecov/codecov-action@13ce06bfc6bbe3ecf90edbbf1bc32fe5978ca1d3 # v5 with: token: ${{ secrets.CODECOV_TOKEN }} From 9149fb82bca09606690d4dbbc018083d9add3a2e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 26 Jan 2025 08:30:58 +0000 Subject: [PATCH 36/55] Update github/codeql-action digest to f6091c0 --- .github/workflows/codeql-analysis.yml | 4 ++-- .github/workflows/ossf-scorecard.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 0d4067fe42a2..e7d55f0dd380 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -34,7 +34,7 @@ jobs: - name: Check out repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Initialize CodeQL - uses: github/codeql-action/init@ee117c905ab18f32fa0f66c2fe40ecc8013f3e04 # v3 + uses: github/codeql-action/init@f6091c0113d1dcf9b98e269ee48e8a7e51b7bdd4 # v3 with: languages: ${{ matrix.language }} tools: linked @@ -47,4 +47,4 @@ jobs: -Dscan.tag.CodeQL \ allMainClasses - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@ee117c905ab18f32fa0f66c2fe40ecc8013f3e04 # v3 + uses: github/codeql-action/analyze@f6091c0113d1dcf9b98e269ee48e8a7e51b7bdd4 # v3 diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index bcd9ff22a8a2..0900c1a0d83f 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -57,6 +57,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@ee117c905ab18f32fa0f66c2fe40ecc8013f3e04 # v3 + uses: github/codeql-action/upload-sarif@f6091c0113d1dcf9b98e269ee48e8a7e51b7bdd4 # v3 with: sarif_file: results.sarif From 4c382d0e7d44c775c5c75fab3fb2b489de825220 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 26 Jan 2025 09:54:12 +0000 Subject: [PATCH 37/55] Update dependency gradle to v8.12.1 --- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e1b837a19c22..d71047787f80 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=7a00d51fb93147819aab76024feece20b6b84e420694101f276be952e08bef03 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip +distributionSha256Sum=8d97a97984f6cbd2b85fe4c60a743440a347544bf18818048e611f5288d46c94 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From 80a22b6262353634c434e6476622e4df6f9f601c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 26 Jan 2025 13:08:14 +0000 Subject: [PATCH 38/55] Update dependency org.apache.groovy:groovy to v4.0.25 (#4273) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 44c5fa83a9d9..7002f24e7a02 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -35,7 +35,7 @@ bndlib = { module = "biz.aQute.bnd:biz.aQute.bndlib", version.ref = "bnd" } checkstyle = { module = "com.puppycrawl.tools:checkstyle", version.ref = "checkstyle" } classgraph = { module = "io.github.classgraph:classgraph", version = "4.8.179" } commons-io = { module = "commons-io:commons-io", version = "2.18.0" } -groovy4 = { module = "org.apache.groovy:groovy", version = "4.0.24" } +groovy4 = { module = "org.apache.groovy:groovy", version = "4.0.25" } groovy2-bom = { module = "org.codehaus.groovy:groovy-bom", version = "2.5.23" } hamcrest = { module = "org.hamcrest:hamcrest", version = "3.0" } jackson-dataformat-yaml = { module = "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml", version.ref = "jackson" } From 7b4b972115f800e90a8cf1afa98ae97edc778377 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 26 Jan 2025 17:50:39 +0100 Subject: [PATCH 39/55] Revert to older Eclipse version to fix Spotless issue on CI --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7002f24e7a02..cfd4ea914cef 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ asciidoctor-plugins = "4.0.4" # Check if workaround in documentation.gradle.kts assertj = "3.27.3" bnd = "7.1.0" checkstyle = "10.21.1" -eclipse = "4.34.0" +eclipse = "4.32.0" jackson = "2.18.2" jacoco = "0.8.12" jmh = "1.37" From ca7bb8022fdd81546a1e053d050c5954e8679591 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 26 Jan 2025 18:44:44 +0100 Subject: [PATCH 40/55] Revert "Revert to older Eclipse version to fix Spotless issue on CI" This reverts commit 7b4b972115f800e90a8cf1afa98ae97edc778377. --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cfd4ea914cef..7002f24e7a02 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ asciidoctor-plugins = "4.0.4" # Check if workaround in documentation.gradle.kts assertj = "3.27.3" bnd = "7.1.0" checkstyle = "10.21.1" -eclipse = "4.32.0" +eclipse = "4.34.0" jackson = "2.18.2" jacoco = "0.8.12" jmh = "1.37" From 060f4db0e03f032ea4b39c042d75052deb4a5486 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 26 Jan 2025 18:58:01 +0100 Subject: [PATCH 41/55] Pass enclosing instance types to `DisplayNameGenerators` (#4266) Prior to this commit, a `DisplayNameGenerator` could access the type of a `@Nested` test class as well as the enclosing class in which a `@Nested` test class is declared, but it could not access the concrete runtime type of the enclosing instance for a `@Nested` test class. When a `DisplayNameGenerator` is used to build hierarchical display names, this could lead to confusing results or even conflicting results depending on the structure of the test classes used, "conflicting" in the sense that two nested test classes may have identical display names that do not represent the runtime structure of the test classes. Now, each `DisplayNameGenerator` receives the list of enclosing instance types when computing the display name of a nested test class or test method. Resolves #4130. --------- Co-authored-by: Sam Brannen <104798+sbrannen@users.noreply.github.com> --- .../release-notes-5.12.0-M1.adoc | 5 +- .../jupiter/api/DisplayNameGenerator.java | 131 +++++++++++++++--- .../engine/descriptor/DisplayNameUtils.java | 19 +-- .../descriptor/MethodBasedTestDescriptor.java | 7 +- .../descriptor/NestedClassTestDescriptor.java | 13 +- .../descriptor/TestFactoryTestDescriptor.java | 5 +- .../descriptor/TestMethodTestDescriptor.java | 6 +- .../TestTemplateTestDescriptor.java | 5 +- .../discovery/ClassSelectorResolver.java | 7 +- .../discovery/MethodSelectorResolver.java | 26 ++-- .../api/DisplayNameGenerationTests.java | 23 ++- ...ntimeEnclosingTypeScenarioOneTestCase.java | 19 +++ ...ntimeEnclosingTypeScenarioTwoTestCase.java | 19 +++ ...SentencesRuntimeEnclosingTypeTestCase.java | 33 +++++ .../parallel/ResourceLockAnnotationTests.java | 5 +- .../CustomDisplayNameGenerator.java | 6 +- .../descriptor/DisplayNameUtilsTests.java | 30 ++-- .../descriptor/ExtensionContextTests.java | 6 +- .../JupiterTestDescriptorTests.java | 24 ++-- .../TestFactoryTestDescriptorTests.java | 3 +- ...TemplateInvocationTestDescriptorTests.java | 3 +- .../TestTemplateTestDescriptorTests.java | 7 +- 22 files changed, 305 insertions(+), 97 deletions(-) create mode 100644 jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeScenarioOneTestCase.java create mode 100644 jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeScenarioTwoTestCase.java create mode 100644 jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeTestCase.java diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc index 5d6ceaea5dc0..0abbde8c9af2 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc @@ -83,7 +83,10 @@ JUnit repository on GitHub. [[release-notes-5.12.0-M1-junit-jupiter-bug-fixes]] ==== Bug Fixes -* ❓ +* Provide _runtime_ enclosing types of `@Nested` test classes and contained test methods + to `DisplayNameGenerator` implementations. Prior to this change, such generators were + only able to access the enclosing class in which `@Nested` was declared, but they could + not access the concrete runtime type of the enclosing instance. [[release-notes-5.12.0-M1-junit-jupiter-deprecations-and-breaking-changes]] ==== Deprecations and Breaking Changes diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java index 810d180e3da8..89f7784d8c57 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java @@ -10,11 +10,15 @@ package org.junit.jupiter.api; +import static java.util.Collections.emptyList; +import static org.apiguardian.api.API.Status.DEPRECATED; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import static org.junit.platform.commons.support.ModifierSupport.isStatic; import java.lang.reflect.Method; +import java.util.List; import java.util.Optional; import java.util.function.Predicate; @@ -74,7 +78,8 @@ public interface DisplayNameGenerator { /** * Generate a display name for the given top-level or {@code static} nested test class. * - *

If it returns {@code null}, the default display name generator will be used instead. + *

If this method returns {@code null}, the default display name + * generator will be used instead. * * @param testClass the class to generate a name for; never {@code null} * @return the display name for the class; never blank @@ -82,19 +87,52 @@ public interface DisplayNameGenerator { String generateDisplayNameForClass(Class testClass); /** - * Generate a display name for the given {@link Nested @Nested} inner test class. + * Generate a display name for the given {@link Nested @Nested} inner test + * class. * - *

If it returns {@code null}, the default display name generator will be used instead. + *

If this method returns {@code null}, the default display name + * generator will be used instead. * * @param nestedClass the class to generate a name for; never {@code null} * @return the display name for the nested class; never blank + * @deprecated in favor of {@link #generateDisplayNameForNestedClass(List, Class)} */ - String generateDisplayNameForNestedClass(Class nestedClass); + @API(status = DEPRECATED, since = "5.12") + @Deprecated + default String generateDisplayNameForNestedClass(Class nestedClass) { + throw new UnsupportedOperationException( + "Implement generateDisplayNameForNestedClass(List>, Class) instead"); + } + + /** + * Generate a display name for the given {@link Nested @Nested} inner test + * class. + * + *

If this method returns {@code null}, the default display name + * generator will be used instead. + * + * @implNote The classes supplied as {@code enclosingInstanceTypes} may + * differ from the classes returned from invocations of + * {@link Class#getEnclosingClass()} — for example, when a nested test + * class is inherited from a superclass. + * + * @param enclosingInstanceTypes the runtime types of the enclosing + * instances for the test class, ordered from outermost to innermost, + * excluding {@code nestedClass}; never {@code null} + * @param nestedClass the class to generate a name for; never {@code null} + * @return the display name for the nested class; never blank + * @since 5.12 + */ + @API(status = EXPERIMENTAL, since = "5.12") + default String generateDisplayNameForNestedClass(List> enclosingInstanceTypes, Class nestedClass) { + return generateDisplayNameForNestedClass(nestedClass); + } /** * Generate a display name for the given method. * - *

If it returns {@code null}, the default display name generator will be used instead. + *

If this method returns {@code null}, the default display name + * generator will be used instead. * * @implNote The class instance supplied as {@code testClass} may differ from * the class returned by {@code testMethod.getDeclaringClass()} — for @@ -103,8 +141,42 @@ public interface DisplayNameGenerator { * @param testClass the class the test method is invoked on; never {@code null} * @param testMethod method to generate a display name for; never {@code null} * @return the display name for the test; never blank + * @deprecated in favor of {@link #generateDisplayNameForMethod(List, Class, Method)} */ - String generateDisplayNameForMethod(Class testClass, Method testMethod); + @API(status = DEPRECATED, since = "5.12") + @Deprecated + default String generateDisplayNameForMethod(Class testClass, Method testMethod) { + throw new UnsupportedOperationException( + "Implement generateDisplayNameForMethod(List>, Class, Method) instead"); + } + + /** + * Generate a display name for the given method. + * + *

If this method returns {@code null}, the default display name + * generator will be used instead. + * + * @implNote The classes supplied as {@code enclosingInstanceTypes} may + * differ from the classes returned from invocations of + * {@link Class#getEnclosingClass()} — for example, when a nested test + * class is inherited from a superclass. Similarly, the class instance + * supplied as {@code testClass} may differ from the class returned by + * {@code testMethod.getDeclaringClass()} — for example, when a test + * method is inherited from a superclass. + * + * @param enclosingInstanceTypes the runtime types of the enclosing + * instances for the test class, ordered from outermost to innermost, + * excluding {@code testClass}; never {@code null} + * @param testClass the class the test method is invoked on; never {@code null} + * @param testMethod method to generate a display name for; never {@code null} + * @return the display name for the test; never blank + * @since 5.12 + */ + @API(status = EXPERIMENTAL, since = "5.12") + default String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { + return generateDisplayNameForMethod(testClass, testMethod); + } /** * Generate a string representation of the formal parameters of the supplied @@ -142,12 +214,13 @@ public String generateDisplayNameForClass(Class testClass) { } @Override - public String generateDisplayNameForNestedClass(Class nestedClass) { + public String generateDisplayNameForNestedClass(List> enclosingInstanceTypes, Class nestedClass) { return nestedClass.getSimpleName(); } @Override - public String generateDisplayNameForMethod(Class testClass, Method testMethod) { + public String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { return testMethod.getName() + parameterTypesAsString(testMethod); } } @@ -168,7 +241,8 @@ public Simple() { } @Override - public String generateDisplayNameForMethod(Class testClass, Method testMethod) { + public String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { String displayName = testMethod.getName(); if (hasParameters(testMethod)) { displayName += ' ' + parameterTypesAsString(testMethod); @@ -202,13 +276,15 @@ public String generateDisplayNameForClass(Class testClass) { } @Override - public String generateDisplayNameForNestedClass(Class nestedClass) { - return replaceUnderscores(super.generateDisplayNameForNestedClass(nestedClass)); + public String generateDisplayNameForNestedClass(List> enclosingInstanceTypes, Class nestedClass) { + return replaceUnderscores(super.generateDisplayNameForNestedClass(enclosingInstanceTypes, nestedClass)); } @Override - public String generateDisplayNameForMethod(Class testClass, Method testMethod) { - return replaceUnderscores(super.generateDisplayNameForMethod(testClass, testMethod)); + public String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { + return replaceUnderscores( + super.generateDisplayNameForMethod(enclosingInstanceTypes, testClass, testMethod)); } private static String replaceUnderscores(String name) { @@ -243,18 +319,21 @@ public String generateDisplayNameForClass(Class testClass) { } @Override - public String generateDisplayNameForNestedClass(Class nestedClass) { - return getSentenceBeginning(nestedClass); + public String generateDisplayNameForNestedClass(List> enclosingInstanceTypes, Class nestedClass) { + return getSentenceBeginning(enclosingInstanceTypes, nestedClass); } @Override - public String generateDisplayNameForMethod(Class testClass, Method testMethod) { - return getSentenceBeginning(testClass) + getFragmentSeparator(testClass) - + getGeneratorFor(testClass).generateDisplayNameForMethod(testClass, testMethod); + public String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { + return getSentenceBeginning(enclosingInstanceTypes, testClass) + getFragmentSeparator(testClass) + + getGeneratorFor(testClass).generateDisplayNameForMethod(enclosingInstanceTypes, testClass, + testMethod); } - private String getSentenceBeginning(Class testClass) { - Class enclosingClass = testClass.getEnclosingClass(); + private String getSentenceBeginning(List> enclosingInstanceTypes, Class testClass) { + Class enclosingClass = enclosingInstanceTypes.isEmpty() ? null + : enclosingInstanceTypes.get(enclosingInstanceTypes.size() - 1); boolean topLevelTestClass = (enclosingClass == null || isStatic(testClass)); Optional displayName = findAnnotation(testClass, DisplayName.class)// .map(DisplayName::value).map(String::trim); @@ -280,10 +359,16 @@ private String getSentenceBeginning(Class testClass) { .filter(IndicativeSentences.class::equals)// .isPresent(); - String prefix = (buildPrefix ? getSentenceBeginning(enclosingClass) + getFragmentSeparator(testClass) : ""); + List> remainingEnclosingInstanceTypes = enclosingInstanceTypes.isEmpty() ? emptyList() + : enclosingInstanceTypes.subList(0, enclosingInstanceTypes.size() - 1); + + String prefix = (buildPrefix + ? getSentenceBeginning(remainingEnclosingInstanceTypes, enclosingClass) + + getFragmentSeparator(testClass) + : ""); - return prefix + displayName.orElseGet( - () -> getGeneratorFor(testClass).generateDisplayNameForNestedClass(testClass)); + return prefix + displayName.orElseGet(() -> getGeneratorFor(testClass).generateDisplayNameForNestedClass( + remainingEnclosingInstanceTypes, testClass)); } /** diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java index ebe3d127bf0c..76b65ef5e49a 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java @@ -14,6 +14,7 @@ import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; +import java.util.List; import java.util.Optional; import java.util.function.Function; import java.util.function.Supplier; @@ -89,10 +90,10 @@ static String determineDisplayName(AnnotatedElement element, Supplier di return displayNameSupplier.get(); } - static String determineDisplayNameForMethod(Class testClass, Method testMethod, - JupiterConfiguration configuration) { + static String determineDisplayNameForMethod(Supplier>> enclosingInstanceTypes, Class testClass, + Method testMethod, JupiterConfiguration configuration) { return determineDisplayName(testMethod, - createDisplayNameSupplierForMethod(testClass, testMethod, configuration)); + createDisplayNameSupplierForMethod(enclosingInstanceTypes, testClass, testMethod, configuration)); } static Supplier createDisplayNameSupplierForClass(Class testClass, JupiterConfiguration configuration) { @@ -100,16 +101,16 @@ static Supplier createDisplayNameSupplierForClass(Class testClass, Ju generator -> generator.generateDisplayNameForClass(testClass)); } - static Supplier createDisplayNameSupplierForNestedClass(Class testClass, - JupiterConfiguration configuration) { + static Supplier createDisplayNameSupplierForNestedClass(Supplier>> enclosingInstanceTypes, + Class testClass, JupiterConfiguration configuration) { return createDisplayNameSupplier(testClass, configuration, - generator -> generator.generateDisplayNameForNestedClass(testClass)); + generator -> generator.generateDisplayNameForNestedClass(enclosingInstanceTypes.get(), testClass)); } - private static Supplier createDisplayNameSupplierForMethod(Class testClass, Method testMethod, - JupiterConfiguration configuration) { + private static Supplier createDisplayNameSupplierForMethod(Supplier>> enclosingInstanceTypes, + Class testClass, Method testMethod, JupiterConfiguration configuration) { return createDisplayNameSupplier(testClass, configuration, - generator -> generator.generateDisplayNameForMethod(testClass, testMethod)); + generator -> generator.generateDisplayNameForMethod(enclosingInstanceTypes.get(), testClass, testMethod)); } private static Supplier createDisplayNameSupplier(Class testClass, JupiterConfiguration configuration, diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java index 525b58293709..52bf3c4ef8b9 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java @@ -21,6 +21,7 @@ import java.util.Optional; import java.util.Set; import java.util.function.Consumer; +import java.util.function.Supplier; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ExtensionContext; @@ -59,9 +60,9 @@ public abstract class MethodBasedTestDescriptor extends JupiterTestDescriptor im private final Set tags; MethodBasedTestDescriptor(UniqueId uniqueId, Class testClass, Method testMethod, - JupiterConfiguration configuration) { - this(uniqueId, determineDisplayNameForMethod(testClass, testMethod, configuration), testClass, testMethod, - configuration); + Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration) { + this(uniqueId, determineDisplayNameForMethod(enclosingInstanceTypes, testClass, testMethod, configuration), + testClass, testMethod, configuration); } MethodBasedTestDescriptor(UniqueId uniqueId, String displayName, Class testClass, Method testMethod, diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java index 6d4dc1e2d088..72d11ce5b09e 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java @@ -19,6 +19,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.function.Supplier; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.TestInstances; @@ -46,8 +47,10 @@ public class NestedClassTestDescriptor extends ClassBasedTestDescriptor { public static final String SEGMENT_TYPE = "nested-class"; - public NestedClassTestDescriptor(UniqueId uniqueId, Class testClass, JupiterConfiguration configuration) { - super(uniqueId, testClass, createDisplayNameSupplierForNestedClass(testClass, configuration), configuration); + public NestedClassTestDescriptor(UniqueId uniqueId, Class testClass, + Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration) { + super(uniqueId, testClass, + createDisplayNameSupplierForNestedClass(enclosingInstanceTypes, testClass, configuration), configuration); } // --- TestDescriptor ------------------------------------------------------ @@ -62,7 +65,11 @@ public final Set getTags() { @Override public List> getEnclosingTestClasses() { - TestDescriptor parent = getParent().orElse(null); + return getEnclosingTestClasses(getParent().orElse(null)); + } + + @API(status = INTERNAL, since = "5.12") + public static List> getEnclosingTestClasses(TestDescriptor parent) { if (parent instanceof ClassBasedTestDescriptor) { ClassBasedTestDescriptor parentClassDescriptor = (ClassBasedTestDescriptor) parent; List> result = new ArrayList<>(parentClassDescriptor.getEnclosingTestClasses()); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java index e4643baa54de..f0d37814bbf2 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java @@ -18,6 +18,7 @@ import java.lang.reflect.Method; import java.net.URI; import java.util.Iterator; +import java.util.List; import java.util.Optional; import java.util.function.Supplier; import java.util.stream.Stream; @@ -63,8 +64,8 @@ public class TestFactoryTestDescriptor extends TestMethodTestDescriptor implemen private final DynamicDescendantFilter dynamicDescendantFilter = new DynamicDescendantFilter(); public TestFactoryTestDescriptor(UniqueId uniqueId, Class testClass, Method testMethod, - JupiterConfiguration configuration) { - super(uniqueId, testClass, testMethod, configuration); + Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration) { + super(uniqueId, testClass, testMethod, enclosingInstanceTypes, configuration); } // --- Filterable ---------------------------------------------------------- diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java index 423f2e3b9013..67fa136a52d1 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java @@ -17,6 +17,8 @@ import static org.junit.platform.commons.util.CollectionUtils.forEachInReverseOrder; import java.lang.reflect.Method; +import java.util.List; +import java.util.function.Supplier; import org.apiguardian.api.API; import org.junit.jupiter.api.TestInstance.Lifecycle; @@ -75,8 +77,8 @@ public class TestMethodTestDescriptor extends MethodBasedTestDescriptor { private final ReflectiveInterceptorCall interceptorCall; public TestMethodTestDescriptor(UniqueId uniqueId, Class testClass, Method testMethod, - JupiterConfiguration configuration) { - super(uniqueId, testClass, testMethod, configuration); + Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration) { + super(uniqueId, testClass, testMethod, enclosingInstanceTypes, configuration); this.interceptorCall = defaultInterceptorCall; } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java index f89c17b26a32..e592ba3a6326 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; import java.util.stream.Stream; import org.apiguardian.api.API; @@ -46,8 +47,8 @@ public class TestTemplateTestDescriptor extends MethodBasedTestDescriptor implem private final DynamicDescendantFilter dynamicDescendantFilter = new DynamicDescendantFilter(); public TestTemplateTestDescriptor(UniqueId uniqueId, Class testClass, Method templateMethod, - JupiterConfiguration configuration) { - super(uniqueId, testClass, templateMethod, configuration); + Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration) { + super(uniqueId, testClass, templateMethod, enclosingInstanceTypes, configuration); } // --- Filterable ---------------------------------------------------------- diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java index 46d97088c358..0f809dcbde47 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java @@ -12,6 +12,7 @@ import static java.util.function.Predicate.isEqual; import static java.util.stream.Collectors.toCollection; +import static org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor.getEnclosingTestClasses; import static org.junit.jupiter.engine.discovery.predicates.IsTestClassWithTests.isTestOrTestFactoryOrTestTemplateMethod; import static org.junit.platform.commons.support.HierarchyTraversalMode.TOP_DOWN; import static org.junit.platform.commons.support.ReflectionSupport.findMethods; @@ -122,9 +123,9 @@ private ClassTestDescriptor newClassTestDescriptor(TestDescriptor parent, Class< } private NestedClassTestDescriptor newNestedClassTestDescriptor(TestDescriptor parent, Class testClass) { - return new NestedClassTestDescriptor( - parent.getUniqueId().append(NestedClassTestDescriptor.SEGMENT_TYPE, testClass.getSimpleName()), testClass, - configuration); + UniqueId uniqueId = parent.getUniqueId().append(NestedClassTestDescriptor.SEGMENT_TYPE, + testClass.getSimpleName()); + return new NestedClassTestDescriptor(uniqueId, testClass, () -> getEnclosingTestClasses(parent), configuration); } private Resolution toResolution(Optional testDescriptor) { diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java index 7f76e2d30fce..9d5af96aa103 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java @@ -166,8 +166,8 @@ private enum MethodType { TEST(new IsTestMethod(), TestMethodTestDescriptor.SEGMENT_TYPE) { @Override protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testClass, Method method, - JupiterConfiguration configuration) { - return new TestMethodTestDescriptor(uniqueId, testClass, method, configuration); + Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration) { + return new TestMethodTestDescriptor(uniqueId, testClass, method, enclosingInstanceTypes, configuration); } }, @@ -176,8 +176,9 @@ protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testCl TestFactoryTestDescriptor.DYNAMIC_TEST_SEGMENT_TYPE) { @Override protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testClass, Method method, - JupiterConfiguration configuration) { - return new TestFactoryTestDescriptor(uniqueId, testClass, method, configuration); + Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration) { + return new TestFactoryTestDescriptor(uniqueId, testClass, method, enclosingInstanceTypes, + configuration); } }, @@ -185,8 +186,9 @@ protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testCl TestTemplateInvocationTestDescriptor.SEGMENT_TYPE) { @Override protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testClass, Method method, - JupiterConfiguration configuration) { - return new TestTemplateTestDescriptor(uniqueId, testClass, method, configuration); + Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration) { + return new TestTemplateTestDescriptor(uniqueId, testClass, method, enclosingInstanceTypes, + configuration); } }; @@ -207,7 +209,7 @@ private Optional resolve(List> enclosingClasses, Class< } return context.addToParent(() -> selectClass(enclosingClasses, testClass), // parent -> Optional.of( - createTestDescriptor(createUniqueId(method, parent), testClass, method, configuration))); + createTestDescriptor((ClassBasedTestDescriptor) parent, testClass, method, configuration))); } private DiscoverySelector selectClass(List> enclosingClasses, Class testClass) { @@ -227,7 +229,7 @@ private Optional resolveUniqueIdIntoTestDescriptor(UniqueId uniq // @formatter:off return methodFinder.findMethod(methodSpecPart, testClass) .filter(methodPredicate) - .map(method -> createTestDescriptor(createUniqueId(method, parent), testClass, method, configuration)); + .map(method -> createTestDescriptor((ClassBasedTestDescriptor) parent, testClass, method, configuration)); // @formatter:on }); } @@ -237,6 +239,12 @@ private Optional resolveUniqueIdIntoTestDescriptor(UniqueId uniq return Optional.empty(); } + private TestDescriptor createTestDescriptor(ClassBasedTestDescriptor parent, Class testClass, Method method, + JupiterConfiguration configuration) { + UniqueId uniqueId = createUniqueId(method, parent); + return createTestDescriptor(uniqueId, testClass, method, parent::getEnclosingTestClasses, configuration); + } + private UniqueId createUniqueId(Method method, TestDescriptor parent) { String methodId = String.format("%s(%s)", method.getName(), ClassUtils.nullSafeToString(method.getParameterTypes())); @@ -244,7 +252,7 @@ private UniqueId createUniqueId(Method method, TestDescriptor parent) { } protected abstract TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testClass, Method method, - JupiterConfiguration configuration); + Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java index 8dbea0cdb42d..c7a1fe90efc6 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java @@ -20,6 +20,7 @@ import java.lang.reflect.Method; import java.util.EmptyStackException; +import java.util.List; import java.util.Stack; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; @@ -197,6 +198,23 @@ void indicativeSentencesGenerationInheritance() { ); } + @Test + void indicativeSentencesRuntimeEnclosingType() { + check(IndicativeSentencesRuntimeEnclosingTypeScenarioOneTestCase.class, // + "CONTAINER: Scenario 1", // + "CONTAINER: Scenario 1 -> Level 1", // + "CONTAINER: Scenario 1 -> Level 1 -> Level 2", // + "TEST: Scenario 1 -> Level 1 -> Level 2 -> this is a test"// + ); + + check(IndicativeSentencesRuntimeEnclosingTypeScenarioTwoTestCase.class, // + "CONTAINER: Scenario 2", // + "CONTAINER: Scenario 2 -> Level 1", // + "CONTAINER: Scenario 2 -> Level 1 -> Level 2", // + "TEST: Scenario 2 -> Level 1 -> Level 2 -> this is a test"// + ); + } + private void check(Class testClass, String... expectedDisplayNames) { var request = request().selectors(selectClass(testClass)).build(); var descriptors = discoverTests(request).getDescendants(); @@ -217,12 +235,13 @@ public String generateDisplayNameForClass(Class testClass) { } @Override - public String generateDisplayNameForNestedClass(Class nestedClass) { + public String generateDisplayNameForNestedClass(List> enclosingInstanceTypes, Class nestedClass) { return "nn"; } @Override - public String generateDisplayNameForMethod(Class testClass, Method testMethod) { + public String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { return "nn"; } } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeScenarioOneTestCase.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeScenarioOneTestCase.java new file mode 100644 index 000000000000..927903c14612 --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeScenarioOneTestCase.java @@ -0,0 +1,19 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +/** + * @since 5.12 + */ +@DisplayName("Scenario 1") +class IndicativeSentencesRuntimeEnclosingTypeScenarioOneTestCase + extends IndicativeSentencesRuntimeEnclosingTypeTestCase { +} diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeScenarioTwoTestCase.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeScenarioTwoTestCase.java new file mode 100644 index 000000000000..c6e2f377db10 --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeScenarioTwoTestCase.java @@ -0,0 +1,19 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +/** + * @since 5.12 + */ +@DisplayName("Scenario 2") +class IndicativeSentencesRuntimeEnclosingTypeScenarioTwoTestCase + extends IndicativeSentencesRuntimeEnclosingTypeTestCase { +} diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeTestCase.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeTestCase.java new file mode 100644 index 000000000000..86244f4cbfcc --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeTestCase.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; + +/** + * @since 5.12 + */ +@DisplayName("Base Scenario") +@IndicativeSentencesGeneration(separator = " -> ", generator = ReplaceUnderscores.class) +abstract class IndicativeSentencesRuntimeEnclosingTypeTestCase { + + @Nested + class Level_1 { + + @Nested + class Level_2 { + + @Test + void this_is_a_test() { + } + } + } +} diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java index 2025c6e0ad49..b095a8d28e7c 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java @@ -20,6 +20,7 @@ import static org.mockito.Mockito.when; import java.lang.reflect.Method; +import java.util.List; import java.util.Set; import java.util.stream.Stream; @@ -255,7 +256,7 @@ private ClassTestDescriptor getClassTestDescriptor(Class testClass) { private Set getMethodResources(Class testClass) { var descriptor = new TestMethodTestDescriptor( // - uniqueId, testClass, getDeclaredTestMethod(testClass), configuration // + uniqueId, testClass, getDeclaredTestMethod(testClass), List::of, configuration // ); descriptor.setParent(getClassTestDescriptor(testClass)); return descriptor.getExclusiveResources(); @@ -271,7 +272,7 @@ private static Method getDeclaredTestMethod(Class testClass) { } private Set getNestedClassResources(Class testClass) { - var descriptor = new NestedClassTestDescriptor(uniqueId, testClass, configuration); + var descriptor = new NestedClassTestDescriptor(uniqueId, testClass, List::of, configuration); descriptor.setParent(getClassTestDescriptor(testClass.getEnclosingClass())); return descriptor.getExclusiveResources(); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/CustomDisplayNameGenerator.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/CustomDisplayNameGenerator.java index f7b8afae102c..6b00ad5b9282 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/CustomDisplayNameGenerator.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/CustomDisplayNameGenerator.java @@ -11,6 +11,7 @@ package org.junit.jupiter.engine.descriptor; import java.lang.reflect.Method; +import java.util.List; import org.junit.jupiter.api.DisplayNameGenerator; @@ -22,12 +23,13 @@ public String generateDisplayNameForClass(Class testClass) { } @Override - public String generateDisplayNameForNestedClass(Class nestedClass) { + public String generateDisplayNameForNestedClass(List> enclosingInstanceTypes, Class nestedClass) { return "nested-class-display-name"; } @Override - public String generateDisplayNameForMethod(Class testClass, Method testMethod) { + public String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { return "method-display-name"; } } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java index a5d019e1f95a..7a9ca9a4a165 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java @@ -15,6 +15,7 @@ import static org.mockito.Mockito.when; import java.lang.reflect.Method; +import java.util.List; import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.LogRecord; @@ -70,7 +71,7 @@ void shouldGetDisplayNameFromSupplierIfNoDisplayNameAnnotationPresent() { @Nested class ClassDisplayNameSupplierTests { - private JupiterConfiguration configuration = mock(); + private final JupiterConfiguration configuration = mock(); @Test void shouldGetDisplayNameFromDisplayNameGenerationAnnotation() { @@ -115,12 +116,12 @@ void shouldFallbackOnDefaultDisplayNameGeneratorWhenNullIsGenerated() { @Nested class NestedClassDisplayNameTests { - private JupiterConfiguration configuration = mock(); + private final JupiterConfiguration configuration = mock(); @Test void shouldGetDisplayNameFromDisplayNameGenerationAnnotation() { when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); - Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForNestedClass( + Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForNestedClass(List::of, StandardDisplayNameTestCase.class, configuration); assertThat(displayName.get()).isEqualTo(StandardDisplayNameTestCase.class.getSimpleName()); @@ -129,7 +130,7 @@ void shouldGetDisplayNameFromDisplayNameGenerationAnnotation() { @Test void shouldGetDisplayNameFromDefaultDisplayNameGenerator() { when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); - Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForNestedClass( + Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForNestedClass(List::of, NestedTestCase.class, configuration); assertThat(displayName.get()).isEqualTo("nested-class-display-name"); @@ -138,7 +139,7 @@ void shouldGetDisplayNameFromDefaultDisplayNameGenerator() { @Test void shouldFallbackOnDefaultDisplayNameGeneratorWhenNullIsGenerated() { when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); - Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForNestedClass( + Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForNestedClass(List::of, NullDisplayNameTestCase.NestedTestCase.class, configuration); assertThat(displayName.get()).isEqualTo("nested-class-display-name"); @@ -148,14 +149,14 @@ void shouldFallbackOnDefaultDisplayNameGeneratorWhenNullIsGenerated() { @Nested class MethodDisplayNameTests { - private JupiterConfiguration configuration = mock(); + private final JupiterConfiguration configuration = mock(); @Test void shouldGetDisplayNameFromDisplayNameGenerationAnnotation() throws Exception { when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); Method method = MyTestCase.class.getDeclaredMethod("test1"); - String displayName = DisplayNameUtils.determineDisplayNameForMethod(StandardDisplayNameTestCase.class, - method, configuration); + String displayName = DisplayNameUtils.determineDisplayNameForMethod(List::of, + StandardDisplayNameTestCase.class, method, configuration); assertThat(displayName).isEqualTo("test1()"); } @@ -165,8 +166,8 @@ void shouldGetDisplayNameFromDefaultNameGenerator() throws Exception { Method method = MyTestCase.class.getDeclaredMethod("test1"); when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); - String displayName = DisplayNameUtils.determineDisplayNameForMethod(NotDisplayNameTestCase.class, method, - configuration); + String displayName = DisplayNameUtils.determineDisplayNameForMethod(List::of, NotDisplayNameTestCase.class, + method, configuration); assertThat(displayName).isEqualTo("method-display-name"); } @@ -176,8 +177,8 @@ void shouldFallbackOnDefaultDisplayNameGeneratorWhenNullIsGenerated() throws Exc Method method = NullDisplayNameTestCase.class.getDeclaredMethod("test"); when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); - String displayName = DisplayNameUtils.determineDisplayNameForMethod(NullDisplayNameTestCase.class, method, - configuration); + String displayName = DisplayNameUtils.determineDisplayNameForMethod(List::of, NullDisplayNameTestCase.class, + method, configuration); assertThat(displayName).isEqualTo("method-display-name"); } @@ -238,12 +239,13 @@ public String generateDisplayNameForClass(Class testClass) { } @Override - public String generateDisplayNameForNestedClass(Class nestedClass) { + public String generateDisplayNameForNestedClass(List> enclosingInstanceTypes, Class nestedClass) { return null; } @Override - public String generateDisplayNameForMethod(Class testClass, Method testMethod) { + public String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { return null; } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java index 482290a3c559..66796f302620 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java @@ -418,7 +418,7 @@ void configurationParameter(Function { var method = ReflectionSupport.findMethod(testClass, "extensionContextFactories").orElseThrow(); var methodUniqueId = UniqueId.parse("[engine:junit-jupiter]/[class:MyClass]/[method:myMethod]"); - var methodTestDescriptor = new TestMethodTestDescriptor(methodUniqueId, testClass, method, + var methodTestDescriptor = new TestMethodTestDescriptor(methodUniqueId, testClass, method, List::of, configuration); return new MethodExtensionContext(null, null, methodTestDescriptor, configuration, extensionRegistry, null); @@ -428,7 +428,7 @@ void configurationParameter(Function testClass = TestCase.class; Method testMethod = testClass.getDeclaredMethod("test"); - TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, testClass, testMethod, + TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, testClass, testMethod, List::of, configuration); assertEquals(uniqueId, descriptor.getUniqueId()); @@ -127,7 +127,7 @@ void constructFromMethodWithAnnotations() throws Exception { JupiterTestDescriptor classDescriptor = new ClassTestDescriptor(uniqueId, TestCase.class, configuration); Method testMethod = TestCase.class.getDeclaredMethod("foo"); TestMethodTestDescriptor methodDescriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, - configuration); + List::of, configuration); classDescriptor.addChild(methodDescriptor); assertEquals(testMethod, methodDescriptor.getTestMethod()); @@ -143,7 +143,7 @@ void constructFromMethodWithAnnotations() throws Exception { void constructFromMethodWithCustomTestAnnotation() throws Exception { Method testMethod = TestCase.class.getDeclaredMethod("customTestAnnotation"); TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, - configuration); + List::of, configuration); assertEquals(testMethod, descriptor.getTestMethod()); assertEquals("custom name", descriptor.getDisplayName(), "display name:"); @@ -155,7 +155,7 @@ void constructFromMethodWithCustomTestAnnotation() throws Exception { void constructFromMethodWithParameters() throws Exception { Method testMethod = TestCase.class.getDeclaredMethod("test", String.class, BigDecimal.class); TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, - configuration); + List::of, configuration); assertEquals(testMethod, descriptor.getTestMethod()); assertEquals("test(String, BigDecimal)", descriptor.getDisplayName(), "display name"); @@ -166,7 +166,7 @@ void constructFromMethodWithParameters() throws Exception { void constructFromMethodWithPrimitiveArrayParameter() throws Exception { Method testMethod = TestCase.class.getDeclaredMethod("test", int[].class); TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, - configuration); + List::of, configuration); assertEquals(testMethod, descriptor.getTestMethod()); assertEquals("test(int[])", descriptor.getDisplayName(), "display name"); @@ -177,7 +177,7 @@ void constructFromMethodWithPrimitiveArrayParameter() throws Exception { void constructFromMethodWithObjectArrayParameter() throws Exception { Method testMethod = TestCase.class.getDeclaredMethod("test", String[].class); TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, - configuration); + List::of, configuration); assertEquals(testMethod, descriptor.getTestMethod()); assertEquals("test(String[])", descriptor.getDisplayName(), "display name"); @@ -188,7 +188,7 @@ void constructFromMethodWithObjectArrayParameter() throws Exception { void constructFromMethodWithMultidimensionalPrimitiveArrayParameter() throws Exception { Method testMethod = TestCase.class.getDeclaredMethod("test", int[][][][][].class); TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, - configuration); + List::of, configuration); assertEquals(testMethod, descriptor.getTestMethod()); assertEquals("test(int[][][][][])", descriptor.getDisplayName(), "display name"); @@ -199,7 +199,7 @@ void constructFromMethodWithMultidimensionalPrimitiveArrayParameter() throws Exc void constructFromMethodWithMultidimensionalObjectArrayParameter() throws Exception { Method testMethod = TestCase.class.getDeclaredMethod("test", String[][][][][].class); TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, - configuration); + List::of, configuration); assertEquals(testMethod, descriptor.getTestMethod()); assertEquals("test(String[][][][][])", descriptor.getDisplayName(), "display name"); @@ -210,7 +210,7 @@ void constructFromMethodWithMultidimensionalObjectArrayParameter() throws Except void constructFromInheritedMethod() throws Exception { Method testMethod = ConcreteTestCase.class.getMethod("theTest"); TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, ConcreteTestCase.class, testMethod, - configuration); + List::of, configuration); assertEquals(testMethod, descriptor.getTestMethod()); @@ -230,7 +230,7 @@ void shouldTakeCustomMethodNameDescriptorFromConfigurationIfPresent() { assertEquals("class-display-name", descriptor.getDisplayName()); assertEquals(getClass().getName(), descriptor.getLegacyReportingName()); - descriptor = new NestedClassTestDescriptor(uniqueId, NestedTestCase.class, configuration); + descriptor = new NestedClassTestDescriptor(uniqueId, NestedTestCase.class, List::of, configuration); assertEquals("nested-class-display-name", descriptor.getDisplayName()); assertEquals(NestedTestCase.class.getName(), descriptor.getLegacyReportingName()); @@ -249,7 +249,7 @@ void defaultDisplayNamesForTestClasses() { assertEquals(getClass().getSimpleName(), descriptor.getDisplayName()); assertEquals(getClass().getName(), descriptor.getLegacyReportingName()); - descriptor = new NestedClassTestDescriptor(uniqueId, NestedTestCase.class, configuration); + descriptor = new NestedClassTestDescriptor(uniqueId, NestedTestCase.class, List::of, configuration); assertEquals(NestedTestCase.class.getSimpleName(), descriptor.getDisplayName()); assertEquals(NestedTestCase.class.getName(), descriptor.getLegacyReportingName()); @@ -269,7 +269,7 @@ void enclosingClassesAreDerivedFromParent() { ClassBasedTestDescriptor parentDescriptor = new ClassTestDescriptor(uniqueId, StaticTestCase.class, configuration); ClassBasedTestDescriptor nestedDescriptor = new NestedClassTestDescriptor(uniqueId, NestedTestCase.class, - configuration); + List::of, configuration); assertThat(parentDescriptor.getEnclosingTestClasses()).isEmpty(); assertThat(nestedDescriptor.getEnclosingTestClasses()).isEmpty(); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java index 618d290870d2..6ad584b728df 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java @@ -18,6 +18,7 @@ import java.io.File; import java.lang.reflect.Method; import java.net.URI; +import java.util.List; import java.util.Optional; import java.util.stream.Stream; @@ -147,7 +148,7 @@ void before() throws Exception { Method testMethod = CustomStreamTestCase.class.getDeclaredMethod("customStream"); descriptor = new TestFactoryTestDescriptor(UniqueId.forEngine("engine"), CustomStreamTestCase.class, - testMethod, jupiterConfiguration); + testMethod, List::of, jupiterConfiguration); when(extensionContext.getTestMethod()).thenReturn(Optional.of(testMethod)); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptorTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptorTests.java index 0491150abb57..65b2bf268688 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptorTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptorTests.java @@ -16,6 +16,7 @@ import static org.mockito.Mockito.when; import java.lang.reflect.Method; +import java.util.List; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; @@ -34,7 +35,7 @@ void invocationsDoNotDeclareExclusiveResources() throws Exception { JupiterConfiguration configuration = mock(); when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new DisplayNameGenerator.Standard()); TestTemplateTestDescriptor parent = new TestTemplateTestDescriptor(UniqueId.root("segment", "template"), - testClass, testTemplateMethod, configuration); + testClass, testTemplateMethod, List::of, configuration); TestTemplateInvocationContext invocationContext = mock(); when(invocationContext.getDisplayName(anyInt())).thenReturn("invocation"); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptorTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptorTests.java index 1cd492db23ce..c0dab4d6a66e 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptorTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptorTests.java @@ -15,6 +15,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.util.List; import java.util.Set; import org.junit.jupiter.api.DisplayNameGenerator; @@ -45,7 +46,7 @@ void inheritsTagsFromParent() throws Exception { TestTemplateTestDescriptor testDescriptor = new TestTemplateTestDescriptor( parentUniqueId.append("tmp", "testTemplate()"), MyTestCase.class, - MyTestCase.class.getDeclaredMethod("testTemplate"), jupiterConfiguration); + MyTestCase.class.getDeclaredMethod("testTemplate"), List::of, jupiterConfiguration); parent.addChild(testDescriptor); assertThat(testDescriptor.getTags()).containsExactlyInAnyOrder(TestTag.create("foo"), TestTag.create("bar"), @@ -63,7 +64,7 @@ void shouldUseCustomDisplayNameGeneratorIfPresentFromConfiguration() throws Exce TestTemplateTestDescriptor testDescriptor = new TestTemplateTestDescriptor( parentUniqueId.append("tmp", "testTemplate()"), MyTestCase.class, - MyTestCase.class.getDeclaredMethod("testTemplate"), jupiterConfiguration); + MyTestCase.class.getDeclaredMethod("testTemplate"), List::of, jupiterConfiguration); parent.addChild(testDescriptor); assertThat(testDescriptor.getDisplayName()).isEqualTo("method-display-name"); @@ -80,7 +81,7 @@ void shouldUseStandardDisplayNameGeneratorIfConfigurationNotPresent() throws Exc TestTemplateTestDescriptor testDescriptor = new TestTemplateTestDescriptor( parentUniqueId.append("tmp", "testTemplate()"), MyTestCase.class, - MyTestCase.class.getDeclaredMethod("testTemplate"), jupiterConfiguration); + MyTestCase.class.getDeclaredMethod("testTemplate"), List::of, jupiterConfiguration); parent.addChild(testDescriptor); assertThat(testDescriptor.getDisplayName()).isEqualTo("testTemplate()"); From 511ab7d04dd5deec372157381b37a42003778b18 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 26 Jan 2025 19:23:11 +0100 Subject: [PATCH 42/55] Provide runtime enclosing instance types to `ResourceLocksProviders` Make API consistent with `DisplayNameGenerator` prior to releasing it in 5.12. Resolves #4163. --------- Co-authored-by: Sam Brannen <104798+sbrannen@users.noreply.github.com> --- .../DynamicSharedResourcesDemo.java | 4 +- .../api/parallel/ResourceLocksProvider.java | 32 +++++++++++--- .../descriptor/ClassTestDescriptor.java | 5 ++- .../descriptor/MethodBasedTestDescriptor.java | 17 ++++++- .../descriptor/NestedClassTestDescriptor.java | 7 ++- .../engine/descriptor/ResourceLockAware.java | 37 +++++++++++++--- .../parallel/ResourceLockAnnotationTests.java | 18 +++++--- .../parallel/ResourceLocksProviderTests.java | 44 ++++++++++++++----- 8 files changed, 128 insertions(+), 36 deletions(-) diff --git a/documentation/src/test/java/example/sharedresources/DynamicSharedResourcesDemo.java b/documentation/src/test/java/example/sharedresources/DynamicSharedResourcesDemo.java index 31f4c37e6bb3..4abe7297e357 100644 --- a/documentation/src/test/java/example/sharedresources/DynamicSharedResourcesDemo.java +++ b/documentation/src/test/java/example/sharedresources/DynamicSharedResourcesDemo.java @@ -19,6 +19,7 @@ import java.lang.reflect.Method; import java.util.Collections; +import java.util.List; import java.util.Properties; import java.util.Set; @@ -68,7 +69,8 @@ void canSetCustomPropertyToBanana() { static class Provider implements ResourceLocksProvider { @Override - public Set provideForMethod(Class testClass, Method testMethod) { + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { ResourceAccessMode mode = testMethod.getName().startsWith("canSet") ? READ_WRITE : READ; return Collections.singleton(new Lock(SYSTEM_PROPERTIES, mode)); } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java index d94024d99f3b..2cffc5d6101f 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java @@ -14,6 +14,7 @@ import static org.apiguardian.api.API.Status.EXPERIMENTAL; import java.lang.reflect.Method; +import java.util.List; import java.util.Objects; import java.util.Set; @@ -43,7 +44,7 @@ public interface ResourceLocksProvider { /** - * Add shared resources to a test class. + * Add shared resources for a test class. * *

Invoked in case a test class or its parent class is annotated with * {@code @ResourceLock(providers)}. @@ -60,8 +61,8 @@ default Set provideForClass(Class testClass) { } /** - * Add shared resources to a {@linkplain org.junit.jupiter.api.Nested @Nested} - * test class. + * Add shared resources for a + * {@link org.junit.jupiter.api.Nested @Nested} test class. * *

Invoked in case: *

    @@ -75,16 +76,24 @@ default Set provideForClass(Class testClass) { * the same semantics as annotating a nested test class with an analogous * {@code @ResourceLock(value, mode)} declaration. * + * @implNote The classes supplied as {@code enclosingInstanceTypes} may + * differ from the classes returned from invocations of + * {@link Class#getEnclosingClass()} — for example, when a nested test + * class is inherited from a superclass. + * + * @param enclosingInstanceTypes the runtime types of the enclosing + * instances for the test class, ordered from outermost to innermost, + * excluding {@code testClass}; never {@code null} * @param testClass a nested test class for which to add shared resources * @return a set of {@link Lock}; may be empty * @see org.junit.jupiter.api.Nested @Nested */ - default Set provideForNestedClass(Class testClass) { + default Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { return emptySet(); } /** - * Add shared resources to a test method. + * Add shared resources for a test method. * *

    Invoked in case: *

      @@ -97,13 +106,24 @@ default Set provideForNestedClass(Class testClass) { * has the same semantics as annotating a test method * with analogous {@code @ResourceLock(value, mode)}. * + * @implNote The classes supplied as {@code enclosingInstanceTypes} may + * differ from the classes returned from invocations of + * {@link Class#getEnclosingClass()} — for example, when a nested test + * class is inherited from a superclass. Similarly, the class instance + * supplied as {@code testClass} may differ from the class returned by + * {@code testMethod.getDeclaringClass()} — for example, when a test + * method is inherited from a superclass. + * + * @param enclosingInstanceTypes the runtime types of the enclosing + * instances for the test class, ordered from outermost to innermost, + * excluding {@code testClass}; never {@code null} * @param testClass the test class or {@link org.junit.jupiter.api.Nested @Nested} * test class that contains the {@code testMethod} * @param testMethod a test method for which to add shared resources * @return a set of {@link Lock}; may be empty * @see org.junit.jupiter.api.Nested @Nested */ - default Set provideForMethod(Class testClass, Method testMethod) { + default Set provideForMethod(List> enclosingInstanceTypes, Class testClass, Method testMethod) { return emptySet(); } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java index 7c2a5571694c..e935db38e782 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.function.Function; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.TestInstances; @@ -79,8 +80,8 @@ protected TestInstances instantiateTestClass(JupiterEngineExecutionContext paren } @Override - public Set evaluateResourceLocksProvider(ResourceLocksProvider provider) { - return provider.provideForClass(getTestClass()); + public Function> getResourceLocksProviderEvaluator() { + return provider -> provider.provideForClass(getTestClass()); } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java index 52bf3c4ef8b9..3a5b785591f8 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java @@ -13,14 +13,17 @@ import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.jupiter.api.parallel.ResourceLockTarget.CHILDREN; import static org.junit.jupiter.engine.descriptor.DisplayNameUtils.determineDisplayNameForMethod; +import static org.junit.jupiter.engine.descriptor.ResourceLockAware.enclosingInstanceTypesDependentResourceLocksProviderEvaluator; import static org.junit.platform.commons.util.CollectionUtils.forEachInReverseOrder; import java.lang.reflect.Method; +import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Supplier; import org.apiguardian.api.API; @@ -97,8 +100,18 @@ public ExclusiveResourceCollector getExclusiveResourceCollector() { } @Override - public Set evaluateResourceLocksProvider(ResourceLocksProvider provider) { - return provider.provideForMethod(getTestClass(), getTestMethod()); + public Function> getResourceLocksProviderEvaluator() { + return enclosingInstanceTypesDependentResourceLocksProviderEvaluator(this::getEnclosingTestClasses, + (provider, enclosingInstanceTypes) -> provider.provideForMethod(enclosingInstanceTypes, getTestClass(), + getTestMethod())); + } + + private List> getEnclosingTestClasses() { + return getParent() // + .filter(ClassBasedTestDescriptor.class::isInstance) // + .map(ClassBasedTestDescriptor.class::cast) // + .map(ClassBasedTestDescriptor::getEnclosingTestClasses) // + .orElseGet(Collections::emptyList); } @Override diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java index 72d11ce5b09e..b0619fa09cff 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java @@ -13,12 +13,14 @@ import static java.util.Collections.emptyList; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.jupiter.engine.descriptor.DisplayNameUtils.createDisplayNameSupplierForNestedClass; +import static org.junit.jupiter.engine.descriptor.ResourceLockAware.enclosingInstanceTypesDependentResourceLocksProviderEvaluator; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.function.Function; import java.util.function.Supplier; import org.apiguardian.api.API; @@ -94,8 +96,9 @@ protected TestInstances instantiateTestClass(JupiterEngineExecutionContext paren } @Override - public Set evaluateResourceLocksProvider(ResourceLocksProvider provider) { - return provider.provideForNestedClass(getTestClass()); + public Function> getResourceLocksProviderEvaluator() { + return enclosingInstanceTypesDependentResourceLocksProviderEvaluator(this::getEnclosingTestClasses, (provider, + enclosingInstanceTypes) -> provider.provideForNestedClass(enclosingInstanceTypes, getTestClass())); } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ResourceLockAware.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ResourceLockAware.java index e7aa88edea52..c4f427989036 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ResourceLockAware.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ResourceLockAware.java @@ -14,7 +14,11 @@ import java.util.ArrayDeque; import java.util.Deque; +import java.util.List; import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Stream; import org.junit.jupiter.api.parallel.ResourceLocksProvider; @@ -35,8 +39,10 @@ default Stream determineExclusiveResources() { parent = parent.getParent().orElse(null); } + Function> evaluator = getResourceLocksProviderEvaluator(); + if (ancestors.isEmpty()) { - return determineOwnExclusiveResources(); + return determineOwnExclusiveResources(evaluator); } Stream parentStaticResourcesForChildren = ancestors.getLast() // @@ -44,18 +50,37 @@ default Stream determineExclusiveResources() { Stream ancestorDynamicResources = ancestors.stream() // .map(ResourceLockAware::getExclusiveResourceCollector) // - .flatMap(collector -> collector.getDynamicResources(this::evaluateResourceLocksProvider)); + .flatMap(collector -> collector.getDynamicResources(evaluator)); - return Stream.of(ancestorDynamicResources, parentStaticResourcesForChildren, determineOwnExclusiveResources())// + return Stream.of(ancestorDynamicResources, parentStaticResourcesForChildren, + determineOwnExclusiveResources(evaluator))// .flatMap(s -> s); } - default Stream determineOwnExclusiveResources() { - return this.getExclusiveResourceCollector().getAllExclusiveResources(this::evaluateResourceLocksProvider); + default Stream determineOwnExclusiveResources( + Function> providerToLocks) { + return this.getExclusiveResourceCollector().getAllExclusiveResources(providerToLocks); } ExclusiveResourceCollector getExclusiveResourceCollector(); - Set evaluateResourceLocksProvider(ResourceLocksProvider provider); + Function> getResourceLocksProviderEvaluator(); + + static Function> enclosingInstanceTypesDependentResourceLocksProviderEvaluator( + Supplier>> enclosingInstanceTypesSupplier, + BiFunction>, Set> evaluator) { + return new Function>() { + + private List> enclosingInstanceTypes; + + @Override + public Set apply(ResourceLocksProvider provider) { + if (this.enclosingInstanceTypes == null) { + this.enclosingInstanceTypes = enclosingInstanceTypesSupplier.get(); + } + return evaluator.apply(provider, this.enclosingInstanceTypes); + } + }; + } } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java index b095a8d28e7c..60e7c36b808f 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java @@ -361,7 +361,8 @@ public Set provideForClass(Class testClass) { } @Override - public Set provideForMethod(Class testClass, Method testMethod) { + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { return Set.of(new Lock("b1")); } } @@ -369,7 +370,8 @@ public Set provideForMethod(Class testClass, Method testMethod) { static class MethodLevelProvider implements ResourceLocksProvider { @Override - public Set provideForMethod(Class testClass, Method testMethod) { + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { return Set.of(new Lock("b2")); } } @@ -377,7 +379,7 @@ public Set provideForMethod(Class testClass, Method testMethod) { static class NestedClassLevelProvider implements ResourceLocksProvider { @Override - public Set provideForNestedClass(Class testClass) { + public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { return Set.of(new Lock("c1"), new Lock("c2", ResourceAccessMode.READ)); } } @@ -417,12 +419,13 @@ public Set provideForClass(Class testClass) { static class SecondClassLevelProvider implements ResourceLocksProvider { @Override - public Set provideForMethod(Class testClass, Method testMethod) { + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { return Set.of(new Lock("b2", ResourceAccessMode.READ)); } @Override - public Set provideForNestedClass(Class testClass) { + public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { return Set.of(new Lock("c2")); } } @@ -430,7 +433,7 @@ public Set provideForNestedClass(Class testClass) { static class NestedClassLevelProvider implements ResourceLocksProvider { @Override - public Set provideForNestedClass(Class testClass) { + public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { return Set.of(new Lock("c3")); } } @@ -452,7 +455,8 @@ void test() { static class Provider implements ResourceLocksProvider { @Override - public Set provideForMethod(Class testClass, Method testMethod) { + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { return Set.of(new Lock("a1")); } } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLocksProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLocksProviderTests.java index 8a73be42b8ee..f5bc7a17b77b 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLocksProviderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLocksProviderTests.java @@ -20,6 +20,7 @@ import static org.junit.platform.testkit.engine.EventConditions.test; import java.lang.reflect.Method; +import java.util.List; import java.util.Set; import java.util.stream.Stream; @@ -60,6 +61,12 @@ void methodLevelProviderInNestedClass() { assertThat(events.filter(event(test(), finishedSuccessfully())::matches)).hasSize(2); } + @Test + void providesAccessToRuntimeEnclosingInstances() { + var events = execute(SubClassLevelProviderTestCase.class); + assertThat(events.filter(event(test(), finishedSuccessfully())::matches)).hasSize(2); + } + private Stream execute(Class testCase) { return executeTestsForClass(testCase).allEvents().stream(); } @@ -108,28 +115,35 @@ static class Provider implements ResourceLocksProvider { private static boolean isProvideForNestedClassCalled = false; private static boolean isProvideForNestedTestMethodCalled = false; + private Class testClass; + @Override public Set provideForClass(Class testClass) { + this.testClass = testClass; isProvideForClassCalled = true; - assertEquals(ClassLevelProviderTestCase.class, testClass); + assertThat(testClass).isAssignableTo(ClassLevelProviderTestCase.class); return emptySet(); } @Override - public Set provideForNestedClass(Class testClass) { + public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { isProvideForNestedClassCalled = true; + assertEquals(List.of(this.testClass), enclosingInstanceTypes); assertEquals(ClassLevelProviderTestCase.NestedClass.class, testClass); return emptySet(); } @Override - public Set provideForMethod(Class testClass, Method testMethod) { - if (testClass == ClassLevelProviderTestCase.class) { + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { + if (ClassLevelProviderTestCase.class.isAssignableFrom(testClass)) { + assertEquals(List.of(), enclosingInstanceTypes); assertEquals("test", testMethod.getName()); isProvideForTestMethodCalled = true; return emptySet(); } if (testClass == ClassLevelProviderTestCase.NestedClass.class) { + assertEquals(List.of(this.testClass), enclosingInstanceTypes); assertEquals("nestedTest", testMethod.getName()); isProvideForNestedTestMethodCalled = true; return emptySet(); @@ -140,6 +154,9 @@ public Set provideForMethod(Class testClass, Method testMethod) { } } + static class SubClassLevelProviderTestCase extends ClassLevelProviderTestCase { + } + @SuppressWarnings("JUnitMalformedDeclaration") static class NestedClassLevelProviderTestCase { @@ -177,15 +194,18 @@ public Set provideForClass(Class testClass) { } @Override - public Set provideForNestedClass(Class testClass) { + public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { isProvideForNestedClassCalled = true; + assertEquals(List.of(NestedClassLevelProviderTestCase.class), enclosingInstanceTypes); assertEquals(NestedClass.class, testClass); return emptySet(); } @Override - public Set provideForMethod(Class testClass, Method testMethod) { + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { isProvideForMethodCalled = true; + assertEquals(List.of(NestedClassLevelProviderTestCase.class), enclosingInstanceTypes); assertEquals(NestedClassLevelProviderTestCase.NestedClass.class, testClass); assertEquals("nestedTest", testMethod.getName()); return emptySet(); @@ -226,14 +246,16 @@ public Set provideForClass(Class testClass) { } @Override - public Set provideForNestedClass(Class testClass) { + public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { fail("'provideForNestedClass' should not be called"); return emptySet(); } @Override - public Set provideForMethod(Class testClass, Method testMethod) { + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { isProvideForMethodCalled = true; + assertEquals(List.of(), enclosingInstanceTypes); assertEquals(MethodLevelProviderTestCase.class, testClass); assertEquals("test", testMethod.getName()); return emptySet(); @@ -274,14 +296,16 @@ public Set provideForClass(Class testClass) { } @Override - public Set provideForNestedClass(Class testClass) { + public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { fail("'provideForNestedClass' should not be called"); return emptySet(); } @Override - public Set provideForMethod(Class testClass, Method testMethod) { + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { isProvideForMethodCalled = true; + assertEquals(List.of(MethodLevelProviderInNestedClassTestCase.class), enclosingInstanceTypes); assertEquals(MethodLevelProviderInNestedClassTestCase.NestedClass.class, testClass); assertEquals("nestedTest", testMethod.getName()); return emptySet(); From b7349b4a92d95e5df627b2cbca5f97ff7e31a9b4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 00:43:38 +0000 Subject: [PATCH 43/55] Update dependency com.puppycrawl.tools:checkstyle to v10.21.2 (#4274) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7002f24e7a02..9069b20ba753 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ asciidoctorj-pdf = "2.3.19" asciidoctor-plugins = "4.0.4" # Check if workaround in documentation.gradle.kts can be removed when upgrading assertj = "3.27.3" bnd = "7.1.0" -checkstyle = "10.21.1" +checkstyle = "10.21.2" eclipse = "4.34.0" jackson = "2.18.2" jacoco = "0.8.12" From 7d661965424007c17f50d02060219a4c0f210f2b Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 27 Jan 2025 08:36:59 +0100 Subject: [PATCH 44/55] Re-open completed non-task/-question issues without assigned milestone --- .github/workflows/sanitize-closed-issues.yml | 62 ++++++++++++++++++++ .github/workflows/unlabel-closed-issues.yml | 45 -------------- 2 files changed, 62 insertions(+), 45 deletions(-) create mode 100644 .github/workflows/sanitize-closed-issues.yml delete mode 100644 .github/workflows/unlabel-closed-issues.yml diff --git a/.github/workflows/sanitize-closed-issues.yml b/.github/workflows/sanitize-closed-issues.yml new file mode 100644 index 000000000000..b87d73100608 --- /dev/null +++ b/.github/workflows/sanitize-closed-issues.yml @@ -0,0 +1,62 @@ +name: Sanitizes assigned labels and milestone on closed issues +on: + issues: + types: + - closed +permissions: read-all +jobs: + label_issues: + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 + with: + script: | + const issue = await github.rest.issues.get({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + }); + const originalLabels = issue.data.labels.map(l => l.name); + const newLabels = originalLabels.filter(l => l !== "status: in progress" && l !== "status: new"); + if (newLabels.length !== originalLabels.length) { + await github.rest.issues.update({ + issue_number: issue.data.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: newLabels, + }); + } + if (issue.data.state_reason === "not_planned") { + const statusLabels = newLabels.filter(l => l.startsWith("status: ")); + if (statusLabels.length === 0) { + await github.rest.issues.createComment({ + issue_number: issue.data.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: "Please assign a status label to this issue.", + }); + await github.rest.issues.update({ + issue_number: issue.data.number, + owner: context.repo.owner, + repo: context.repo.repo, + state: "open", + }); + } + } else { + if (!(newLabels.includes("type: task") || newLabels.includes("type: question")) && !issue.data.milestone) { + await github.rest.issues.createComment({ + issue_number: issue.data.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: "Please assign a milestone to this issue or label it with `type: task` or `type: question`.", + }); + await github.rest.issues.update({ + issue_number: issue.data.number, + owner: context.repo.owner, + repo: context.repo.repo, + state: "open", + }); + } + } diff --git a/.github/workflows/unlabel-closed-issues.yml b/.github/workflows/unlabel-closed-issues.yml deleted file mode 100644 index 00a4aa525f9c..000000000000 --- a/.github/workflows/unlabel-closed-issues.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: Check/remove status labels from closed issues -on: - issues: - types: - - closed -permissions: read-all -jobs: - label_issues: - runs-on: ubuntu-latest - permissions: - issues: write - steps: - - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 - with: - script: | - const issue = await github.rest.issues.get({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - }); - const originalLabels = issue.data.labels.map(l => l.name); - const newLabels = originalLabels.filter(l => l !== "status: in progress" && l !== "status: new"); - if (newLabels.length !== originalLabels.length) { - await github.rest.issues.update({ - issue_number: issue.data.number, - owner: context.repo.owner, - repo: context.repo.repo, - labels: newLabels, - }); - } - const statusLabels = newLabels.filter(l => l.startsWith("status: ")); - if (issue.data.state_reason === "not_planned" && statusLabels.length === 0) { - await github.rest.issues.createComment({ - issue_number: issue.data.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: "Please assign a status label to this issue.", - }); - await github.rest.issues.update({ - issue_number: issue.data.number, - owner: context.repo.owner, - repo: context.repo.repo, - state: "open", - }); - } From e6446ff9270bf9801265f32888339cba9e489e4c Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 27 Jan 2025 08:37:36 +0100 Subject: [PATCH 45/55] Remove milestone assignment from issues closed as "not planned" --- .github/workflows/sanitize-closed-issues.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/sanitize-closed-issues.yml b/.github/workflows/sanitize-closed-issues.yml index b87d73100608..6f0721a0d2db 100644 --- a/.github/workflows/sanitize-closed-issues.yml +++ b/.github/workflows/sanitize-closed-issues.yml @@ -29,6 +29,14 @@ jobs: }); } if (issue.data.state_reason === "not_planned") { + if (issue.data.milestone) { + await github.rest.issues.update({ + issue_number: issue.data.number, + owner: context.repo.owner, + repo: context.repo.repo, + milestone: null, + }); + } const statusLabels = newLabels.filter(l => l.startsWith("status: ")); if (statusLabels.length === 0) { await github.rest.issues.createComment({ From f93b1fc0dfe8fcad7cd5fd4a2d994e5cef9f2996 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 27 Jan 2025 11:12:46 +0100 Subject: [PATCH 46/55] Support `@TempDir` constructor injection for Java record classes (#4279) The `@TempDir` annotation on a constructor parameter is copied to the generated `final` instance field. This caused it top be picked up by `TempDirectory` for instance field injection which then failed because the field was `final`. This change now checks if a test class is an instance of a record type and skips instance field injection. Since records cannot have any instance fields besides the ones generated from their constructor, there's no need to inject values into any of them. --- .../engine/extension/TempDirectory.java | 5 ++++- .../commons/util/ReflectionUtils.java | 19 +++++++++++++++++++ .../TempDirectoryPerDeclarationTests.java | 14 ++++++++++++++ .../commons/util/ReflectionUtilsTests.java | 17 +++++++++++++++++ 4 files changed, 54 insertions(+), 1 deletion(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java index b35dc58c20d8..357edb731c6e 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java @@ -19,6 +19,7 @@ import static org.junit.platform.commons.support.AnnotationSupport.findAnnotatedFields; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import static org.junit.platform.commons.support.ReflectionSupport.makeAccessible; +import static org.junit.platform.commons.util.ReflectionUtils.isRecordObject; import java.io.File; import java.io.IOException; @@ -135,7 +136,9 @@ private void injectStaticFields(ExtensionContext context, Class testClass) { } private void injectInstanceFields(ExtensionContext context, Object instance) { - injectFields(context, instance, instance.getClass(), ModifierSupport::isNotStatic); + if (!isRecordObject(instance)) { + injectFields(context, instance, instance.getClass(), ModifierSupport::isNotStatic); + } } private void injectFields(ExtensionContext context, Object testInstance, Class testClass, diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java index d0c9921d92e3..01f0e89d0fd2 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java @@ -365,6 +365,25 @@ public static boolean isInnerClass(Class clazz) { return !isStatic(clazz) && clazz.isMemberClass(); } + /** + * {@return whether the supplied {@code object} is an instance of a record class} + * @since 1.12 + */ + @API(status = INTERNAL, since = "1.12") + public static boolean isRecordObject(Object object) { + return object != null && isRecordClass(object.getClass()); + } + + /** + * {@return whether the supplied {@code clazz} is a record class} + * @since 1.12 + */ + @API(status = INTERNAL, since = "1.12") + public static boolean isRecordClass(Class clazz) { + Class superclass = clazz.getSuperclass(); + return superclass != null && "java.lang.Record".equals(superclass.getName()); + } + /** * Determine if the return type of the supplied method is primitive {@code void}. * diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerDeclarationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerDeclarationTests.java index 2deb303768b9..5eddab66bcad 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerDeclarationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerDeclarationTests.java @@ -164,6 +164,12 @@ void resolvesSeparateTempDirsForEachAnnotationDeclaration(TestInstance.Lifecycle assertThat(testATempDirs).doesNotContainEntry("afterEach2", testBTempDirs.get("afterEach2")); } + @Test + void supportsConstructorInjectionOnRecords() { + executeTestsForClass(TempDirRecordTestCase.class).testEvents()// + .assertStatistics(stats -> stats.started(1).succeeded(1)); + } + @Test @DisplayName("does not prevent constructor parameter resolution") void tempDirectoryDoesNotPreventConstructorParameterResolution() { @@ -1527,4 +1533,12 @@ void test(@SuppressWarnings("unused") @TempDir Path tempDir) { } + @SuppressWarnings("JUnitMalformedDeclaration") + record TempDirRecordTestCase(@TempDir Path tempDir) { + @Test + void shouldExists() { + assertTrue(Files.exists(tempDir)); + } + } + } diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java index fb45c4835725..6b89d7aaeb1d 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java @@ -285,6 +285,23 @@ void getInterfaceMethodIfPossible() throws Exception { assertThat(interfaceMethod.getDeclaringClass()).isEqualTo(Closeable.class); } + @Test + void isRecordObject() { + assertTrue(ReflectionUtils.isRecordObject(new SomeRecord(1))); + assertFalse(ReflectionUtils.isRecordObject(new ClassWithOneCustomConstructor(""))); + assertFalse(ReflectionUtils.isRecordObject(null)); + } + + @Test + void isRecordClass() { + assertTrue(ReflectionUtils.isRecordClass(SomeRecord.class)); + assertFalse(ReflectionUtils.isRecordClass(ClassWithOneCustomConstructor.class)); + assertFalse(ReflectionUtils.isRecordClass(Object.class)); + } + + record SomeRecord(int n) { + } + static class ClassWithVoidAndNonVoidMethods { void voidMethod() { From 54f199675d996b7be95fc6a63e0fc0955271fae1 Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Mon, 27 Jan 2025 19:44:38 +0900 Subject: [PATCH 47/55] Apply comment Issue: #2816 --- .../platform/engine/EngineDiscoveryRequest.java | 3 +++ .../junit/platform/engine/ExecutionRequest.java | 7 +++++++ .../java/org/junit/platform/engine/Namespace.java | 2 +- .../java/org/junit/platform/launcher/Launcher.java | 3 --- .../launcher/LauncherDiscoveryRequest.java | 4 ++++ .../launcher/core/DefaultDiscoveryRequest.java | 14 ++++++++++++-- .../platform/launcher/core/DefaultLauncher.java | 5 ----- .../launcher/core/DefaultLauncherSession.java | 5 ----- .../platform/launcher/core/DelegatingLauncher.java | 7 ------- .../core/LauncherDiscoveryRequestBuilder.java | 7 ++++++- .../launcher/core/SessionPerRequestLauncher.java | 9 --------- 11 files changed, 33 insertions(+), 33 deletions(-) diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java index 447790814427..f1227c78d999 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java @@ -18,6 +18,7 @@ import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; import org.junit.platform.engine.reporting.OutputDirectoryProvider; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; /** * {@code EngineDiscoveryRequest} provides a {@link TestEngine} access to the @@ -94,4 +95,6 @@ default OutputDirectoryProvider getOutputDirectoryProvider() { throw new JUnitException( "OutputDirectoryProvider not available; probably due to unaligned versions of the junit-platform-engine and junit-platform-launcher jars on the classpath/module path."); } + + NamespacedHierarchicalStore getStore(); } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/ExecutionRequest.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/ExecutionRequest.java index affe5464a184..34bceb6497db 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/ExecutionRequest.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/ExecutionRequest.java @@ -14,6 +14,7 @@ import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; /** * Provides a single {@link TestEngine} access to the information necessary to @@ -33,6 +34,8 @@ public class ExecutionRequest { private final TestDescriptor rootTestDescriptor; private final EngineExecutionListener engineExecutionListener; private final ConfigurationParameters configurationParameters; + // TODO[#4252] get store from constructor + private final NamespacedHierarchicalStore store = new NamespacedHierarchicalStore<>(null); @API(status = INTERNAL, since = "1.0") public ExecutionRequest(TestDescriptor rootTestDescriptor, EngineExecutionListener engineExecutionListener, @@ -87,4 +90,8 @@ public ConfigurationParameters getConfigurationParameters() { return this.configurationParameters; } + public NamespacedHierarchicalStore getStore() { + return this.store; + } + } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/Namespace.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/Namespace.java index 7fe2d37cb2f8..f0a85782badf 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/Namespace.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/Namespace.java @@ -71,7 +71,7 @@ public int hashCode() { * existing sequence of parts in this namespace. * * @return new namespace; never {@code null} - * @since 5.8 + * @since 5.13 */ @API(status = STABLE, since = "5.13") public Namespace append(Object... parts) { diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java index 6c4e05176cd8..3b46078278ca 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java @@ -13,8 +13,6 @@ import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; -import org.junit.platform.engine.Namespace; -import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; /** * The {@code Launcher} API is the main entry point for client code that @@ -129,5 +127,4 @@ public interface Launcher { @API(status = STABLE, since = "1.4") void execute(TestPlan testPlan, TestExecutionListener... listeners); - NamespacedHierarchicalStore getStore(LauncherDiscoveryRequest launcherDiscoveryRequest); } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryRequest.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryRequest.java index 058281b9b375..99384860a41a 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryRequest.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryRequest.java @@ -19,6 +19,8 @@ import org.junit.platform.engine.DiscoveryFilter; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.EngineDiscoveryRequest; +import org.junit.platform.engine.Namespace; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; /** * {@code LauncherDiscoveryRequest} extends the {@link EngineDiscoveryRequest} API @@ -96,4 +98,6 @@ default LauncherDiscoveryListener getDiscoveryListener() { return LauncherDiscoveryListener.NOOP; } + NamespacedHierarchicalStore getStore(); + } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultDiscoveryRequest.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultDiscoveryRequest.java index 6bd73c4b641e..7621225c7e58 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultDiscoveryRequest.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultDiscoveryRequest.java @@ -20,7 +20,9 @@ import org.junit.platform.engine.DiscoveryFilter; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.EngineDiscoveryRequest; +import org.junit.platform.engine.Namespace; import org.junit.platform.engine.reporting.OutputDirectoryProvider; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.EngineFilter; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; @@ -54,14 +56,17 @@ final class DefaultDiscoveryRequest implements LauncherDiscoveryRequest { private final OutputDirectoryProvider outputDirectoryProvider; + private final NamespacedHierarchicalStore store; + DefaultDiscoveryRequest(List selectors, List engineFilters, List> discoveryFilters, List postDiscoveryFilters, - LauncherConfigurationParameters configurationParameters, LauncherDiscoveryListener discoveryListener, - OutputDirectoryProvider outputDirectoryProvider) { + NamespacedHierarchicalStore store, LauncherConfigurationParameters configurationParameters, + LauncherDiscoveryListener discoveryListener, OutputDirectoryProvider outputDirectoryProvider) { this.selectors = selectors; this.engineFilters = engineFilters; this.discoveryFilters = discoveryFilters; this.postDiscoveryFilters = postDiscoveryFilters; + this.store = store; this.configurationParameters = configurationParameters; this.discoveryListener = discoveryListener; this.outputDirectoryProvider = outputDirectoryProvider; @@ -99,6 +104,11 @@ public LauncherDiscoveryListener getDiscoveryListener() { return this.discoveryListener; } + @Override + public NamespacedHierarchicalStore getStore() { + return this.store; + } + @Override public OutputDirectoryProvider getOutputDirectoryProvider() { return this.outputDirectoryProvider; diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java index cbdc9023896d..df39f9d27164 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java @@ -102,11 +102,6 @@ public void execute(TestPlan testPlan, TestExecutionListener... listeners) { execute((InternalTestPlan) testPlan, listeners); } - @Override - public NamespacedHierarchicalStore getStore(LauncherDiscoveryRequest launcherDiscoveryRequest) { - return new NamespacedHierarchicalStore<>(this.sessionStore); - } - private LauncherDiscoveryResult discover(LauncherDiscoveryRequest discoveryRequest, EngineDiscoveryOrchestrator.Phase phase) { return discoveryOrchestrator.discover(discoveryRequest, phase); diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java index 09df7277989e..c2b54bed66dc 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java @@ -118,11 +118,6 @@ public void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, TestExecu public void execute(TestPlan testPlan, TestExecutionListener... listeners) { throw new PreconditionViolationException("Launcher session has already been closed"); } - - @Override - public NamespacedHierarchicalStore getStore(LauncherDiscoveryRequest launcherDiscoveryRequest) { - throw new PreconditionViolationException("Launcher session has already been closed"); - } } private static LauncherInterceptor composite(List interceptors) { diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncher.java index 297f85e02ee2..3cc6be2316f0 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncher.java @@ -10,8 +10,6 @@ package org.junit.platform.launcher.core; -import org.junit.platform.engine.Namespace; -import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; @@ -54,9 +52,4 @@ public void execute(TestPlan testPlan, TestExecutionListener... listeners) { delegate.execute(testPlan, listeners); } - @Override - public NamespacedHierarchicalStore getStore(LauncherDiscoveryRequest launcherDiscoveryRequest) { - return delegate.getStore(launcherDiscoveryRequest); - } - } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java index 5f6e9f5fe119..1775fd5e8e80 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java @@ -29,7 +29,9 @@ import org.junit.platform.engine.DiscoveryFilter; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.Filter; +import org.junit.platform.engine.Namespace; import org.junit.platform.engine.reporting.OutputDirectoryProvider; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.EngineFilter; import org.junit.platform.launcher.LauncherConstants; import org.junit.platform.launcher.LauncherDiscoveryListener; @@ -110,6 +112,8 @@ public final class LauncherDiscoveryRequestBuilder { private final Map configurationParameters = new HashMap<>(); private final List configurationParametersResources = new ArrayList<>(); private final List discoveryListeners = new ArrayList<>(); + // TODO[#4252] Use the session-level store as its parent. + private final NamespacedHierarchicalStore store = new NamespacedHierarchicalStore<>(null); private boolean implicitConfigurationParametersEnabled = true; private ConfigurationParameters parentConfigurationParameters; private OutputDirectoryProvider outputDirectoryProvider; @@ -336,7 +340,8 @@ public LauncherDiscoveryRequest build() { LauncherDiscoveryListener discoveryListener = getLauncherDiscoveryListener(launcherConfigurationParameters); OutputDirectoryProvider outputDirectoryProvider = getOutputDirectoryProvider(launcherConfigurationParameters); return new DefaultDiscoveryRequest(this.selectors, this.engineFilters, this.discoveryFilters, - this.postDiscoveryFilters, launcherConfigurationParameters, discoveryListener, outputDirectoryProvider); + this.postDiscoveryFilters, this.store, launcherConfigurationParameters, discoveryListener, + outputDirectoryProvider); } private OutputDirectoryProvider getOutputDirectoryProvider( diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java index 1aaa71af400a..dffde014867a 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java @@ -13,8 +13,6 @@ import java.util.List; import java.util.function.Supplier; -import org.junit.platform.engine.Namespace; -import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; @@ -73,13 +71,6 @@ public void execute(TestPlan testPlan, TestExecutionListener... listeners) { } } - @Override - public NamespacedHierarchicalStore getStore(LauncherDiscoveryRequest launcherDiscoveryRequest) { - try (LauncherSession session = createSession()) { - return session.getLauncher().getStore(launcherDiscoveryRequest); - } - } - private LauncherSession createSession() { LauncherSession session = new DefaultLauncherSession(interceptorFactory.get(), sessionListenerSupplier, launcherSupplier); From 3fa27e9434a443a9027b6c4696db845c7a8d48fe Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 27 Jan 2025 11:58:57 +0100 Subject: [PATCH 48/55] Add "Getting Started" section (#4280) --- CONTRIBUTING.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 081a337257e9..135a6cd3456c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,14 @@ # Contributing +## Getting Started + +We welcome new contributors to the project! +If you're interested, please check for [issues labeled with `up-for-grabs` +that are not yet in progress](https://github.com/junit-team/junit5/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20label%3Aup-for-grabs%20-label%3A%22status%3A%20in%20progress%22). +Generally, before you work on an issue, post a comment and ask whether it can be started. +Please wait for the core team to respond and assign the issue to you before making any code +changes. + ## JUnit Contributor License Agreement - You will only Submit Contributions where You have authored 100% of the content. From 516a80123bd66bb7d1f95322cc3f679cbcbf4325 Mon Sep 17 00:00:00 2001 From: Yongjun Hong Date: Sun, 22 Dec 2024 13:32:47 +0900 Subject: [PATCH 49/55] Generate ResourceContext for store resources Issue: #2816 Signed-off-by: yongjunhong --- .../descriptor/AbstractResourceContext.java | 62 +++++++++ .../descriptor/NamespaceAwareStore.java | 91 ++++++++++++++ .../engine/support/store/ResourceContext.java | 118 ++++++++++++++++++ .../store/ResourceContextException.java | 22 ++++ 4 files changed, 293 insertions(+) create mode 100644 junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/AbstractResourceContext.java create mode 100644 junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/NamespaceAwareStore.java create mode 100644 junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/ResourceContext.java create mode 100644 junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/ResourceContextException.java diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/AbstractResourceContext.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/AbstractResourceContext.java new file mode 100644 index 000000000000..6be2c52b0be5 --- /dev/null +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/AbstractResourceContext.java @@ -0,0 +1,62 @@ +/* + * Copyright 2015-2024 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.descriptor; + +import java.util.Optional; + +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; +import org.junit.platform.engine.support.store.ResourceContext; +import org.junit.platform.engine.support.store.ResourceContext.Store.CloseableResource; + +public class AbstractResourceContext implements ResourceContext, AutoCloseable { + + private static final NamespacedHierarchicalStore.CloseAction CLOSE_RESOURCES = (__, ___, value) -> { + if (value instanceof CloseableResource) { + ((CloseableResource) value).close(); + } + }; + + private final ResourceContext parent; + private final NamespacedHierarchicalStore valueStore; + + public AbstractResourceContext(ResourceContext parent) { + this.parent = parent; + this.valueStore = createStore(parent); + } + + private static NamespacedHierarchicalStore createStore(ResourceContext parent) { + NamespacedHierarchicalStore parentStore = null; + if (parent != null) { + parentStore = ((AbstractResourceContext) parent).valueStore; + } + + return new NamespacedHierarchicalStore<>(parentStore, CLOSE_RESOURCES); + } + + @Override + public void close() throws Exception { + this.valueStore.close(); + } + + @Override + public Optional getParent() { + return Optional.ofNullable(parent); + } + + @Override + public ResourceContext getRoot() { + if (this.parent != null) { + return this.parent.getRoot(); + } + return this; + } + +} diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/NamespaceAwareStore.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/NamespaceAwareStore.java new file mode 100644 index 000000000000..032846e061d3 --- /dev/null +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/NamespaceAwareStore.java @@ -0,0 +1,91 @@ +/* + * Copyright 2015-2024 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.descriptor; + +import static org.junit.platform.engine.support.store.ResourceContext.Namespace; + +import java.util.function.Function; +import java.util.function.Supplier; + +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStoreException; +import org.junit.platform.engine.support.store.ResourceContext.Store; +import org.junit.platform.engine.support.store.ResourceContextException; + +public class NamespaceAwareStore implements Store { + + private final NamespacedHierarchicalStore valuesStore; + private final Namespace namespace; + + public NamespaceAwareStore(NamespacedHierarchicalStore valuesStore, Namespace namespace) { + this.valuesStore = valuesStore; + this.namespace = namespace; + } + + @Override + public Object get(Object key) { + Preconditions.notNull(key, "key must not be null"); + return accessStore(() -> this.valuesStore.get(this.namespace, key)); + } + + @Override + public V get(Object key, Class requiredType) { + Preconditions.notNull(key, "key must not be null"); + Preconditions.notNull(requiredType, "requiredType must not be null"); + return accessStore(() -> this.valuesStore.get(this.namespace, key, requiredType)); + } + + @Override + public Object getOrComputeIfAbsent(K key, Function defaultCreator) { + Preconditions.notNull(key, "key must not be null"); + Preconditions.notNull(defaultCreator, "defaultCreator function must not be null"); + return accessStore(() -> this.valuesStore.getOrComputeIfAbsent(this.namespace, key, defaultCreator)); + } + + @Override + public V getOrComputeIfAbsent(K key, Function defaultCreator, Class requiredType) { + Preconditions.notNull(key, "key must not be null"); + Preconditions.notNull(defaultCreator, "defaultCreator function must not be null"); + Preconditions.notNull(requiredType, "requiredType must not be null"); + return accessStore( + () -> this.valuesStore.getOrComputeIfAbsent(this.namespace, key, defaultCreator, requiredType)); + } + + @Override + public void put(Object key, Object value) { + Preconditions.notNull(key, "key must not be null"); + accessStore(() -> this.valuesStore.put(this.namespace, key, value)); + } + + @Override + public Object remove(Object key) { + Preconditions.notNull(key, "key must not be null"); + return accessStore(() -> this.valuesStore.remove(this.namespace, key)); + } + + @Override + public T remove(Object key, Class requiredType) { + Preconditions.notNull(key, "key must not be null"); + Preconditions.notNull(requiredType, "requiredType must not be null"); + return accessStore(() -> this.valuesStore.remove(this.namespace, key, requiredType)); + } + + private T accessStore(Supplier action) { + try { + return action.get(); + } + catch (NamespacedHierarchicalStoreException e) { + throw new ResourceContextException(e.getMessage(), e); + } + } + +} diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/ResourceContext.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/ResourceContext.java new file mode 100644 index 000000000000..de031b33000c --- /dev/null +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/ResourceContext.java @@ -0,0 +1,118 @@ +/* + * Copyright 2015-2024 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.store; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; + +import org.apiguardian.api.API; +import org.junit.platform.commons.support.ReflectionSupport; +import org.junit.platform.commons.util.Preconditions; + +public interface ResourceContext { + + Optional getParent(); + + ResourceContext getRoot(); + + // TODO: Implement methods to retrieve session and request + // Optional getSession(); + // Optional getRequest(); + + // TODO : Implement methods to retrieve lifecycle about session and request + // Optional getSessionLifecycle(); + // Optional getRequestLifecycle(); + + interface Store { + + @API(status = STABLE, since = "5.1") + interface CloseableResource { + + void close() throws Throwable; + + } + + Object get(Object key); + + V get(Object key, Class requiredType); + + default V getOrDefault(Object key, Class requiredType, V defaultValue) { + V value = get(key, requiredType); + return (value != null ? value : defaultValue); + } + + @API(status = STABLE, since = "5.1") + default V getOrComputeIfAbsent(Class type) { + return getOrComputeIfAbsent(type, ReflectionSupport::newInstance, type); + } + + Object getOrComputeIfAbsent(K key, Function defaultCreator); + + V getOrComputeIfAbsent(K key, Function defaultCreator, Class requiredType); + + void put(Object key, Object value); + + Object remove(Object key); + + V remove(Object key, Class requiredType); + + } + + class Namespace { + + public static final Namespace GLOBAL = Namespace.create(new Object()); + + public static Namespace create(Object... parts) { + Preconditions.notEmpty(parts, "parts array must not be null or empty"); + Preconditions.containsNoNullElements(parts, "individual parts must not be null"); + return new Namespace(new ArrayList<>(Arrays.asList(parts))); + } + + private final List parts; + + private Namespace(List parts) { + this.parts = parts; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Namespace that = (Namespace) o; + return this.parts.equals(that.parts); + } + + @Override + public int hashCode() { + return this.parts.hashCode(); + } + + public Namespace append(Object... parts) { + Preconditions.notEmpty(parts, "parts array must not be null or empty"); + Preconditions.containsNoNullElements(parts, "individual parts must not be null"); + ArrayList newParts = new ArrayList<>(this.parts.size() + parts.length); + newParts.addAll(this.parts); + Collections.addAll(newParts, parts); + return new Namespace(newParts); + } + } + +} diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/ResourceContextException.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/ResourceContextException.java new file mode 100644 index 000000000000..604725ef9b69 --- /dev/null +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/ResourceContextException.java @@ -0,0 +1,22 @@ +/* + * Copyright 2015-2024 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.store; + +public class ResourceContextException extends RuntimeException { + public ResourceContextException(String message) { + super(message); + } + + public ResourceContextException(String message, Throwable cause) { + super(message, cause); + } + +} From 339c956706d6b40eef1820bca832ea88e8c37cf9 Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Sun, 22 Dec 2024 14:04:06 +0900 Subject: [PATCH 50/55] Add getStore method in LauncherSession Issue: #2816 Signed-off-by: yongjunhong --- .../org/junit/platform/launcher/LauncherSession.java | 4 ++++ .../platform/launcher/core/DefaultLauncherSession.java | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java index c78ed7761888..57b443e95da3 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java @@ -11,8 +11,10 @@ package org.junit.platform.launcher; import static org.apiguardian.api.API.Status.STABLE; +import static org.junit.platform.engine.support.store.ResourceContext.*; import org.apiguardian.api.API; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.core.LauncherFactory; /** @@ -47,4 +49,6 @@ public interface LauncherSession extends AutoCloseable { @Override void close(); + NamespacedHierarchicalStore getStore(); + } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java index 018eb41cf8a5..09101d9e45ea 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java @@ -10,10 +10,13 @@ package org.junit.platform.launcher.core; +import static org.junit.platform.engine.support.store.ResourceContext.*; + import java.util.List; import java.util.function.Supplier; import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; @@ -43,6 +46,7 @@ public void close() { private final LauncherInterceptor interceptor; private final LauncherSessionListener listener; private final DelegatingLauncher launcher; + private final NamespacedHierarchicalStore store; DefaultLauncherSession(List interceptors, Supplier listenerSupplier, Supplier launcherSupplier) { @@ -58,6 +62,7 @@ public void close() { } this.launcher = new DelegatingLauncher(launcher); listener.launcherSessionOpened(this); + this.store = new NamespacedHierarchicalStore<>(null); } @Override @@ -78,6 +83,11 @@ public void close() { } } + @Override + public NamespacedHierarchicalStore getStore() { + return store; + } + private static class ClosedLauncher implements Launcher { static final ClosedLauncher INSTANCE = new ClosedLauncher(); From f92d60f04bbf4b1f76108a2dd9c3743623ebeb91 Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Mon, 6 Jan 2025 21:17:31 +0900 Subject: [PATCH 51/55] Add Namespace by reusing Jupiter's Namespace class Issue: #2816 --- .../org/junit/platform/engine/Namespace.java | 85 +++++++++++++ .../descriptor/AbstractResourceContext.java | 62 --------- .../descriptor/NamespaceAwareStore.java | 91 -------------- .../engine/support/store/ResourceContext.java | 118 ------------------ .../store/ResourceContextException.java | 22 ---- .../platform/launcher/LauncherSession.java | 2 +- .../launcher/core/DefaultLauncherSession.java | 3 +- 7 files changed, 87 insertions(+), 296 deletions(-) create mode 100644 junit-platform-engine/src/main/java/org/junit/platform/engine/Namespace.java delete mode 100644 junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/AbstractResourceContext.java delete mode 100644 junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/NamespaceAwareStore.java delete mode 100644 junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/ResourceContext.java delete mode 100644 junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/ResourceContextException.java diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/Namespace.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/Namespace.java new file mode 100644 index 000000000000..b97bf19a341a --- /dev/null +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/Namespace.java @@ -0,0 +1,85 @@ +/* + * Copyright 2015-2024 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine; + +import static org.apiguardian.api.API.Status.STABLE; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; + +@API(status = STABLE, since = "5.13") +public class Namespace { + + /** + * The default, global namespace which allows access to stored data from + * all extensions. + */ + public static final Namespace GLOBAL = Namespace.create(new Object()); + + /** + * Create a namespace which restricts access to data to all extensions + * which use the same sequence of {@code parts} for creating a namespace. + * + *

      The order of the {@code parts} is significant. + * + *

      Internally the {@code parts} are compared using {@link Object#equals(Object)}. + */ + public static Namespace create(Object... parts) { + Preconditions.notEmpty(parts, "parts array must not be null or empty"); + Preconditions.containsNoNullElements(parts, "individual parts must not be null"); + return new Namespace(new ArrayList<>(Arrays.asList(parts))); + } + + private final List parts; + + private Namespace(List parts) { + this.parts = parts; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Namespace that = (Namespace) o; + return this.parts.equals(that.parts); + } + + @Override + public int hashCode() { + return this.parts.hashCode(); + } + + /** + * Create a new namespace by appending the supplied {@code parts} to the + * existing sequence of parts in this namespace. + * + * @return new namespace; never {@code null} + * @since 5.8 + */ + @API(status = STABLE, since = "5.13") + public Namespace append(Object... parts) { + Preconditions.notEmpty(parts, "parts array must not be null or empty"); + Preconditions.containsNoNullElements(parts, "individual parts must not be null"); + ArrayList newParts = new ArrayList<>(this.parts.size() + parts.length); + newParts.addAll(this.parts); + Collections.addAll(newParts, parts); + return new Namespace(newParts); + } +} diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/AbstractResourceContext.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/AbstractResourceContext.java deleted file mode 100644 index 6be2c52b0be5..000000000000 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/AbstractResourceContext.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2015-2024 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.descriptor; - -import java.util.Optional; - -import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; -import org.junit.platform.engine.support.store.ResourceContext; -import org.junit.platform.engine.support.store.ResourceContext.Store.CloseableResource; - -public class AbstractResourceContext implements ResourceContext, AutoCloseable { - - private static final NamespacedHierarchicalStore.CloseAction CLOSE_RESOURCES = (__, ___, value) -> { - if (value instanceof CloseableResource) { - ((CloseableResource) value).close(); - } - }; - - private final ResourceContext parent; - private final NamespacedHierarchicalStore valueStore; - - public AbstractResourceContext(ResourceContext parent) { - this.parent = parent; - this.valueStore = createStore(parent); - } - - private static NamespacedHierarchicalStore createStore(ResourceContext parent) { - NamespacedHierarchicalStore parentStore = null; - if (parent != null) { - parentStore = ((AbstractResourceContext) parent).valueStore; - } - - return new NamespacedHierarchicalStore<>(parentStore, CLOSE_RESOURCES); - } - - @Override - public void close() throws Exception { - this.valueStore.close(); - } - - @Override - public Optional getParent() { - return Optional.ofNullable(parent); - } - - @Override - public ResourceContext getRoot() { - if (this.parent != null) { - return this.parent.getRoot(); - } - return this; - } - -} diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/NamespaceAwareStore.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/NamespaceAwareStore.java deleted file mode 100644 index 032846e061d3..000000000000 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/NamespaceAwareStore.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2015-2024 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.descriptor; - -import static org.junit.platform.engine.support.store.ResourceContext.Namespace; - -import java.util.function.Function; -import java.util.function.Supplier; - -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; -import org.junit.platform.engine.support.store.NamespacedHierarchicalStoreException; -import org.junit.platform.engine.support.store.ResourceContext.Store; -import org.junit.platform.engine.support.store.ResourceContextException; - -public class NamespaceAwareStore implements Store { - - private final NamespacedHierarchicalStore valuesStore; - private final Namespace namespace; - - public NamespaceAwareStore(NamespacedHierarchicalStore valuesStore, Namespace namespace) { - this.valuesStore = valuesStore; - this.namespace = namespace; - } - - @Override - public Object get(Object key) { - Preconditions.notNull(key, "key must not be null"); - return accessStore(() -> this.valuesStore.get(this.namespace, key)); - } - - @Override - public V get(Object key, Class requiredType) { - Preconditions.notNull(key, "key must not be null"); - Preconditions.notNull(requiredType, "requiredType must not be null"); - return accessStore(() -> this.valuesStore.get(this.namespace, key, requiredType)); - } - - @Override - public Object getOrComputeIfAbsent(K key, Function defaultCreator) { - Preconditions.notNull(key, "key must not be null"); - Preconditions.notNull(defaultCreator, "defaultCreator function must not be null"); - return accessStore(() -> this.valuesStore.getOrComputeIfAbsent(this.namespace, key, defaultCreator)); - } - - @Override - public V getOrComputeIfAbsent(K key, Function defaultCreator, Class requiredType) { - Preconditions.notNull(key, "key must not be null"); - Preconditions.notNull(defaultCreator, "defaultCreator function must not be null"); - Preconditions.notNull(requiredType, "requiredType must not be null"); - return accessStore( - () -> this.valuesStore.getOrComputeIfAbsent(this.namespace, key, defaultCreator, requiredType)); - } - - @Override - public void put(Object key, Object value) { - Preconditions.notNull(key, "key must not be null"); - accessStore(() -> this.valuesStore.put(this.namespace, key, value)); - } - - @Override - public Object remove(Object key) { - Preconditions.notNull(key, "key must not be null"); - return accessStore(() -> this.valuesStore.remove(this.namespace, key)); - } - - @Override - public T remove(Object key, Class requiredType) { - Preconditions.notNull(key, "key must not be null"); - Preconditions.notNull(requiredType, "requiredType must not be null"); - return accessStore(() -> this.valuesStore.remove(this.namespace, key, requiredType)); - } - - private T accessStore(Supplier action) { - try { - return action.get(); - } - catch (NamespacedHierarchicalStoreException e) { - throw new ResourceContextException(e.getMessage(), e); - } - } - -} diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/ResourceContext.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/ResourceContext.java deleted file mode 100644 index de031b33000c..000000000000 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/ResourceContext.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2015-2024 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.store; - -import static org.apiguardian.api.API.Status.STABLE; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.function.Function; - -import org.apiguardian.api.API; -import org.junit.platform.commons.support.ReflectionSupport; -import org.junit.platform.commons.util.Preconditions; - -public interface ResourceContext { - - Optional getParent(); - - ResourceContext getRoot(); - - // TODO: Implement methods to retrieve session and request - // Optional getSession(); - // Optional getRequest(); - - // TODO : Implement methods to retrieve lifecycle about session and request - // Optional getSessionLifecycle(); - // Optional getRequestLifecycle(); - - interface Store { - - @API(status = STABLE, since = "5.1") - interface CloseableResource { - - void close() throws Throwable; - - } - - Object get(Object key); - - V get(Object key, Class requiredType); - - default V getOrDefault(Object key, Class requiredType, V defaultValue) { - V value = get(key, requiredType); - return (value != null ? value : defaultValue); - } - - @API(status = STABLE, since = "5.1") - default V getOrComputeIfAbsent(Class type) { - return getOrComputeIfAbsent(type, ReflectionSupport::newInstance, type); - } - - Object getOrComputeIfAbsent(K key, Function defaultCreator); - - V getOrComputeIfAbsent(K key, Function defaultCreator, Class requiredType); - - void put(Object key, Object value); - - Object remove(Object key); - - V remove(Object key, Class requiredType); - - } - - class Namespace { - - public static final Namespace GLOBAL = Namespace.create(new Object()); - - public static Namespace create(Object... parts) { - Preconditions.notEmpty(parts, "parts array must not be null or empty"); - Preconditions.containsNoNullElements(parts, "individual parts must not be null"); - return new Namespace(new ArrayList<>(Arrays.asList(parts))); - } - - private final List parts; - - private Namespace(List parts) { - this.parts = parts; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Namespace that = (Namespace) o; - return this.parts.equals(that.parts); - } - - @Override - public int hashCode() { - return this.parts.hashCode(); - } - - public Namespace append(Object... parts) { - Preconditions.notEmpty(parts, "parts array must not be null or empty"); - Preconditions.containsNoNullElements(parts, "individual parts must not be null"); - ArrayList newParts = new ArrayList<>(this.parts.size() + parts.length); - newParts.addAll(this.parts); - Collections.addAll(newParts, parts); - return new Namespace(newParts); - } - } - -} diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/ResourceContextException.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/ResourceContextException.java deleted file mode 100644 index 604725ef9b69..000000000000 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/ResourceContextException.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2015-2024 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.platform.engine.support.store; - -public class ResourceContextException extends RuntimeException { - public ResourceContextException(String message) { - super(message); - } - - public ResourceContextException(String message, Throwable cause) { - super(message, cause); - } - -} diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java index 57b443e95da3..782607dd54b2 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java @@ -11,9 +11,9 @@ package org.junit.platform.launcher; import static org.apiguardian.api.API.Status.STABLE; -import static org.junit.platform.engine.support.store.ResourceContext.*; import org.apiguardian.api.API; +import org.junit.platform.engine.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.core.LauncherFactory; diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java index 09101d9e45ea..c2b54bed66dc 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java @@ -10,12 +10,11 @@ package org.junit.platform.launcher.core; -import static org.junit.platform.engine.support.store.ResourceContext.*; - import java.util.List; import java.util.function.Supplier; import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.engine.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; From 96d86c26edad9f6fcd4d1d07066a8881f80eaddb Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Sat, 18 Jan 2025 20:38:09 +0900 Subject: [PATCH 52/55] Apply comment Issue: #2816 --- .../api/extension/ExtensionContext.java | 22 +++++++++++++++++++ .../descriptor/AbstractExtensionContext.java | 10 +++++++++ .../org/junit/platform/engine/Namespace.java | 2 +- .../store/NamespacedHierarchicalStore.java | 13 +++++++++++ 4 files changed, 46 insertions(+), 1 deletion(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java index 59fece5cd109..3d5ec178f015 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java @@ -418,6 +418,28 @@ default void publishReportEntry(String value) { */ Store getStore(Namespace namespace); + /** + * Returns the store for session-level data. + * + *

      This store is used to store data that is scoped to the session level. + * The data stored in this store will be available throughout the entire session. + * + * @return the store for session-level data + * @since 5.13 + */ + Store getSessionLevelStore(); + + /** + * Returns the store for request-level data. + * + *

      This store is used to store data that is scoped to the request level. + * The data stored in this store will be available only for the duration of the current request. + * + * @return the store for request-level data + * @since 5.13 + */ + Store getRequestLevelStore(); + /** * Get the {@link ExecutionMode} associated with the current test or container. * diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java index bf7cb5d9a059..fd9b6dae734a 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java @@ -192,6 +192,16 @@ public Store getStore(Namespace namespace) { return new NamespaceAwareStore(this.valuesStore, namespace); } + @Override + public Store getSessionLevelStore() { + return getStore(Namespace.create("session")); + } + + @Override + public Store getRequestLevelStore() { + return getStore(Namespace.create("request")); + } + @Override public Set getTags() { // return modifiable copy diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/Namespace.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/Namespace.java index b97bf19a341a..7fe2d37cb2f8 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/Namespace.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/Namespace.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * Copyright 2015-2025 the original author or authors. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v2.0 which diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStore.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStore.java index 9167dde42b13..6b5d1af79af7 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStore.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStore.java @@ -17,6 +17,7 @@ import java.util.Comparator; import java.util.Objects; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; @@ -85,6 +86,18 @@ public NamespacedHierarchicalStore newChild() { return new NamespacedHierarchicalStore<>(this, this.closeAction); } + /** + * Returns the parent store of this {@code NamespacedHierarchicalStore}. + * + *

      If this store does not have a parent, an empty {@code Optional} is returned. + * + * @return an {@code Optional} containing the parent store, or an empty {@code Optional} if there is no parent + * @since 5.13 + */ + public Optional> getParent() { + return Optional.ofNullable(this.parentStore); + } + /** * Determine if this store has been {@linkplain #close() closed}. * From 82c14beae38a448d07a4344be0d57bca15b879f1 Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Sun, 19 Jan 2025 19:19:20 +0900 Subject: [PATCH 53/55] Adding a request-level store to Launcher Issue: #2816 --- .../org/junit/platform/launcher/Launcher.java | 3 +++ .../platform/launcher/core/DefaultLauncher.java | 15 ++++++++++++++- .../launcher/core/DefaultLauncherSession.java | 5 +++++ .../launcher/core/DelegatingLauncher.java | 7 +++++++ .../platform/launcher/core/LauncherFactory.java | 2 +- .../launcher/core/SessionPerRequestLauncher.java | 9 +++++++++ 6 files changed, 39 insertions(+), 2 deletions(-) diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java index 3b46078278ca..6c4e05176cd8 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java @@ -13,6 +13,8 @@ import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; +import org.junit.platform.engine.Namespace; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; /** * The {@code Launcher} API is the main entry point for client code that @@ -127,4 +129,5 @@ public interface Launcher { @API(status = STABLE, since = "1.4") void execute(TestPlan testPlan, TestExecutionListener... listeners); + NamespacedHierarchicalStore getStore(LauncherDiscoveryRequest launcherDiscoveryRequest); } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java index 5ebc4f9a638e..cbdc9023896d 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java @@ -17,10 +17,13 @@ import java.util.Collection; import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.Namespace; import org.junit.platform.engine.TestEngine; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.LauncherSession; import org.junit.platform.launcher.PostDiscoveryFilter; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestPlan; @@ -41,6 +44,8 @@ class DefaultLauncher implements Launcher { private final EngineExecutionOrchestrator executionOrchestrator = new EngineExecutionOrchestrator( listenerRegistry.testExecutionListeners); private final EngineDiscoveryOrchestrator discoveryOrchestrator; + private final LauncherSession launcherSession; + private final NamespacedHierarchicalStore sessionStore; /** * Construct a new {@code DefaultLauncher} with the supplied test engines. @@ -50,7 +55,8 @@ class DefaultLauncher implements Launcher { * @param postDiscoveryFilters the additional post discovery filters for * discovery requests; never {@code null} */ - DefaultLauncher(Iterable testEngines, Collection postDiscoveryFilters) { + DefaultLauncher(Iterable testEngines, Collection postDiscoveryFilters, + LauncherConfig config) { Preconditions.condition(testEngines != null && testEngines.iterator().hasNext(), () -> "Cannot create Launcher without at least one TestEngine; " + "consider adding an engine implementation JAR to the classpath"); @@ -59,6 +65,8 @@ class DefaultLauncher implements Launcher { "PostDiscoveryFilter array must not contain null elements"); this.discoveryOrchestrator = new EngineDiscoveryOrchestrator(testEngines, unmodifiableCollection(postDiscoveryFilters), listenerRegistry.launcherDiscoveryListeners); + this.launcherSession = LauncherFactory.openSession(config); + this.sessionStore = launcherSession.getStore(); } @Override @@ -94,6 +102,11 @@ public void execute(TestPlan testPlan, TestExecutionListener... listeners) { execute((InternalTestPlan) testPlan, listeners); } + @Override + public NamespacedHierarchicalStore getStore(LauncherDiscoveryRequest launcherDiscoveryRequest) { + return new NamespacedHierarchicalStore<>(this.sessionStore); + } + private LauncherDiscoveryResult discover(LauncherDiscoveryRequest discoveryRequest, EngineDiscoveryOrchestrator.Phase phase) { return discoveryOrchestrator.discover(discoveryRequest, phase); diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java index c2b54bed66dc..09df7277989e 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java @@ -118,6 +118,11 @@ public void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, TestExecu public void execute(TestPlan testPlan, TestExecutionListener... listeners) { throw new PreconditionViolationException("Launcher session has already been closed"); } + + @Override + public NamespacedHierarchicalStore getStore(LauncherDiscoveryRequest launcherDiscoveryRequest) { + throw new PreconditionViolationException("Launcher session has already been closed"); + } } private static LauncherInterceptor composite(List interceptors) { diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncher.java index 3cc6be2316f0..297f85e02ee2 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncher.java @@ -10,6 +10,8 @@ package org.junit.platform.launcher.core; +import org.junit.platform.engine.Namespace; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; @@ -52,4 +54,9 @@ public void execute(TestPlan testPlan, TestExecutionListener... listeners) { delegate.execute(testPlan, listeners); } + @Override + public NamespacedHierarchicalStore getStore(LauncherDiscoveryRequest launcherDiscoveryRequest) { + return delegate.getStore(launcherDiscoveryRequest); + } + } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java index c756f27351f0..c6cdb93d7c2d 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java @@ -134,7 +134,7 @@ private static DefaultLauncher createDefaultLauncher(LauncherConfig config, Set engines = collectTestEngines(config); List filters = collectPostDiscoveryFilters(config); - DefaultLauncher launcher = new DefaultLauncher(engines, filters); + DefaultLauncher launcher = new DefaultLauncher(engines, filters, config); registerLauncherDiscoveryListeners(config, launcher); registerTestExecutionListeners(config, launcher, configurationParameters); diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java index dffde014867a..1aaa71af400a 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java @@ -13,6 +13,8 @@ import java.util.List; import java.util.function.Supplier; +import org.junit.platform.engine.Namespace; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; @@ -71,6 +73,13 @@ public void execute(TestPlan testPlan, TestExecutionListener... listeners) { } } + @Override + public NamespacedHierarchicalStore getStore(LauncherDiscoveryRequest launcherDiscoveryRequest) { + try (LauncherSession session = createSession()) { + return session.getLauncher().getStore(launcherDiscoveryRequest); + } + } + private LauncherSession createSession() { LauncherSession session = new DefaultLauncherSession(interceptorFactory.get(), sessionListenerSupplier, launcherSupplier); From 4de6db89e4286a37ca932babb5ea451cf5ba2be2 Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Mon, 27 Jan 2025 19:44:38 +0900 Subject: [PATCH 54/55] Apply comment Issue: #2816 --- .../platform/engine/EngineDiscoveryRequest.java | 3 +++ .../java/org/junit/platform/engine/Namespace.java | 2 +- .../java/org/junit/platform/launcher/Launcher.java | 3 --- .../launcher/LauncherDiscoveryRequest.java | 4 ++++ .../launcher/core/DefaultDiscoveryRequest.java | 14 ++++++++++++-- .../platform/launcher/core/DefaultLauncher.java | 5 ----- .../launcher/core/DefaultLauncherSession.java | 5 ----- .../platform/launcher/core/DelegatingLauncher.java | 7 ------- .../core/LauncherDiscoveryRequestBuilder.java | 7 ++++++- .../launcher/core/SessionPerRequestLauncher.java | 9 --------- 10 files changed, 26 insertions(+), 33 deletions(-) diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java index 447790814427..f1227c78d999 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java @@ -18,6 +18,7 @@ import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; import org.junit.platform.engine.reporting.OutputDirectoryProvider; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; /** * {@code EngineDiscoveryRequest} provides a {@link TestEngine} access to the @@ -94,4 +95,6 @@ default OutputDirectoryProvider getOutputDirectoryProvider() { throw new JUnitException( "OutputDirectoryProvider not available; probably due to unaligned versions of the junit-platform-engine and junit-platform-launcher jars on the classpath/module path."); } + + NamespacedHierarchicalStore getStore(); } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/Namespace.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/Namespace.java index 7fe2d37cb2f8..f0a85782badf 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/Namespace.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/Namespace.java @@ -71,7 +71,7 @@ public int hashCode() { * existing sequence of parts in this namespace. * * @return new namespace; never {@code null} - * @since 5.8 + * @since 5.13 */ @API(status = STABLE, since = "5.13") public Namespace append(Object... parts) { diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java index 6c4e05176cd8..3b46078278ca 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java @@ -13,8 +13,6 @@ import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; -import org.junit.platform.engine.Namespace; -import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; /** * The {@code Launcher} API is the main entry point for client code that @@ -129,5 +127,4 @@ public interface Launcher { @API(status = STABLE, since = "1.4") void execute(TestPlan testPlan, TestExecutionListener... listeners); - NamespacedHierarchicalStore getStore(LauncherDiscoveryRequest launcherDiscoveryRequest); } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryRequest.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryRequest.java index 058281b9b375..99384860a41a 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryRequest.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryRequest.java @@ -19,6 +19,8 @@ import org.junit.platform.engine.DiscoveryFilter; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.EngineDiscoveryRequest; +import org.junit.platform.engine.Namespace; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; /** * {@code LauncherDiscoveryRequest} extends the {@link EngineDiscoveryRequest} API @@ -96,4 +98,6 @@ default LauncherDiscoveryListener getDiscoveryListener() { return LauncherDiscoveryListener.NOOP; } + NamespacedHierarchicalStore getStore(); + } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultDiscoveryRequest.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultDiscoveryRequest.java index 6bd73c4b641e..7621225c7e58 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultDiscoveryRequest.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultDiscoveryRequest.java @@ -20,7 +20,9 @@ import org.junit.platform.engine.DiscoveryFilter; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.EngineDiscoveryRequest; +import org.junit.platform.engine.Namespace; import org.junit.platform.engine.reporting.OutputDirectoryProvider; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.EngineFilter; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; @@ -54,14 +56,17 @@ final class DefaultDiscoveryRequest implements LauncherDiscoveryRequest { private final OutputDirectoryProvider outputDirectoryProvider; + private final NamespacedHierarchicalStore store; + DefaultDiscoveryRequest(List selectors, List engineFilters, List> discoveryFilters, List postDiscoveryFilters, - LauncherConfigurationParameters configurationParameters, LauncherDiscoveryListener discoveryListener, - OutputDirectoryProvider outputDirectoryProvider) { + NamespacedHierarchicalStore store, LauncherConfigurationParameters configurationParameters, + LauncherDiscoveryListener discoveryListener, OutputDirectoryProvider outputDirectoryProvider) { this.selectors = selectors; this.engineFilters = engineFilters; this.discoveryFilters = discoveryFilters; this.postDiscoveryFilters = postDiscoveryFilters; + this.store = store; this.configurationParameters = configurationParameters; this.discoveryListener = discoveryListener; this.outputDirectoryProvider = outputDirectoryProvider; @@ -99,6 +104,11 @@ public LauncherDiscoveryListener getDiscoveryListener() { return this.discoveryListener; } + @Override + public NamespacedHierarchicalStore getStore() { + return this.store; + } + @Override public OutputDirectoryProvider getOutputDirectoryProvider() { return this.outputDirectoryProvider; diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java index cbdc9023896d..df39f9d27164 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java @@ -102,11 +102,6 @@ public void execute(TestPlan testPlan, TestExecutionListener... listeners) { execute((InternalTestPlan) testPlan, listeners); } - @Override - public NamespacedHierarchicalStore getStore(LauncherDiscoveryRequest launcherDiscoveryRequest) { - return new NamespacedHierarchicalStore<>(this.sessionStore); - } - private LauncherDiscoveryResult discover(LauncherDiscoveryRequest discoveryRequest, EngineDiscoveryOrchestrator.Phase phase) { return discoveryOrchestrator.discover(discoveryRequest, phase); diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java index 09df7277989e..c2b54bed66dc 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java @@ -118,11 +118,6 @@ public void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, TestExecu public void execute(TestPlan testPlan, TestExecutionListener... listeners) { throw new PreconditionViolationException("Launcher session has already been closed"); } - - @Override - public NamespacedHierarchicalStore getStore(LauncherDiscoveryRequest launcherDiscoveryRequest) { - throw new PreconditionViolationException("Launcher session has already been closed"); - } } private static LauncherInterceptor composite(List interceptors) { diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncher.java index 297f85e02ee2..3cc6be2316f0 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncher.java @@ -10,8 +10,6 @@ package org.junit.platform.launcher.core; -import org.junit.platform.engine.Namespace; -import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; @@ -54,9 +52,4 @@ public void execute(TestPlan testPlan, TestExecutionListener... listeners) { delegate.execute(testPlan, listeners); } - @Override - public NamespacedHierarchicalStore getStore(LauncherDiscoveryRequest launcherDiscoveryRequest) { - return delegate.getStore(launcherDiscoveryRequest); - } - } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java index 5f6e9f5fe119..1775fd5e8e80 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java @@ -29,7 +29,9 @@ import org.junit.platform.engine.DiscoveryFilter; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.Filter; +import org.junit.platform.engine.Namespace; import org.junit.platform.engine.reporting.OutputDirectoryProvider; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.EngineFilter; import org.junit.platform.launcher.LauncherConstants; import org.junit.platform.launcher.LauncherDiscoveryListener; @@ -110,6 +112,8 @@ public final class LauncherDiscoveryRequestBuilder { private final Map configurationParameters = new HashMap<>(); private final List configurationParametersResources = new ArrayList<>(); private final List discoveryListeners = new ArrayList<>(); + // TODO[#4252] Use the session-level store as its parent. + private final NamespacedHierarchicalStore store = new NamespacedHierarchicalStore<>(null); private boolean implicitConfigurationParametersEnabled = true; private ConfigurationParameters parentConfigurationParameters; private OutputDirectoryProvider outputDirectoryProvider; @@ -336,7 +340,8 @@ public LauncherDiscoveryRequest build() { LauncherDiscoveryListener discoveryListener = getLauncherDiscoveryListener(launcherConfigurationParameters); OutputDirectoryProvider outputDirectoryProvider = getOutputDirectoryProvider(launcherConfigurationParameters); return new DefaultDiscoveryRequest(this.selectors, this.engineFilters, this.discoveryFilters, - this.postDiscoveryFilters, launcherConfigurationParameters, discoveryListener, outputDirectoryProvider); + this.postDiscoveryFilters, this.store, launcherConfigurationParameters, discoveryListener, + outputDirectoryProvider); } private OutputDirectoryProvider getOutputDirectoryProvider( diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java index 1aaa71af400a..dffde014867a 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java @@ -13,8 +13,6 @@ import java.util.List; import java.util.function.Supplier; -import org.junit.platform.engine.Namespace; -import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; @@ -73,13 +71,6 @@ public void execute(TestPlan testPlan, TestExecutionListener... listeners) { } } - @Override - public NamespacedHierarchicalStore getStore(LauncherDiscoveryRequest launcherDiscoveryRequest) { - try (LauncherSession session = createSession()) { - return session.getLauncher().getStore(launcherDiscoveryRequest); - } - } - private LauncherSession createSession() { LauncherSession session = new DefaultLauncherSession(interceptorFactory.get(), sessionListenerSupplier, launcherSupplier); From 628f8cb385b6fda7db39c8f7c3aaeca29ed471a3 Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Mon, 27 Jan 2025 20:24:29 +0900 Subject: [PATCH 55/55] Revert "Apply comment" This reverts commit 4de6db89e4286a37ca932babb5ea451cf5ba2be2. --- .../platform/engine/EngineDiscoveryRequest.java | 3 --- .../java/org/junit/platform/engine/Namespace.java | 2 +- .../java/org/junit/platform/launcher/Launcher.java | 3 +++ .../launcher/LauncherDiscoveryRequest.java | 4 ---- .../launcher/core/DefaultDiscoveryRequest.java | 14 ++------------ .../platform/launcher/core/DefaultLauncher.java | 5 +++++ .../launcher/core/DefaultLauncherSession.java | 5 +++++ .../platform/launcher/core/DelegatingLauncher.java | 7 +++++++ .../core/LauncherDiscoveryRequestBuilder.java | 7 +------ .../launcher/core/SessionPerRequestLauncher.java | 9 +++++++++ 10 files changed, 33 insertions(+), 26 deletions(-) diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java index f1227c78d999..447790814427 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java @@ -18,7 +18,6 @@ import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; import org.junit.platform.engine.reporting.OutputDirectoryProvider; -import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; /** * {@code EngineDiscoveryRequest} provides a {@link TestEngine} access to the @@ -95,6 +94,4 @@ default OutputDirectoryProvider getOutputDirectoryProvider() { throw new JUnitException( "OutputDirectoryProvider not available; probably due to unaligned versions of the junit-platform-engine and junit-platform-launcher jars on the classpath/module path."); } - - NamespacedHierarchicalStore getStore(); } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/Namespace.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/Namespace.java index f0a85782badf..7fe2d37cb2f8 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/Namespace.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/Namespace.java @@ -71,7 +71,7 @@ public int hashCode() { * existing sequence of parts in this namespace. * * @return new namespace; never {@code null} - * @since 5.13 + * @since 5.8 */ @API(status = STABLE, since = "5.13") public Namespace append(Object... parts) { diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java index 3b46078278ca..6c4e05176cd8 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java @@ -13,6 +13,8 @@ import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; +import org.junit.platform.engine.Namespace; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; /** * The {@code Launcher} API is the main entry point for client code that @@ -127,4 +129,5 @@ public interface Launcher { @API(status = STABLE, since = "1.4") void execute(TestPlan testPlan, TestExecutionListener... listeners); + NamespacedHierarchicalStore getStore(LauncherDiscoveryRequest launcherDiscoveryRequest); } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryRequest.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryRequest.java index 99384860a41a..058281b9b375 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryRequest.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryRequest.java @@ -19,8 +19,6 @@ import org.junit.platform.engine.DiscoveryFilter; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.EngineDiscoveryRequest; -import org.junit.platform.engine.Namespace; -import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; /** * {@code LauncherDiscoveryRequest} extends the {@link EngineDiscoveryRequest} API @@ -98,6 +96,4 @@ default LauncherDiscoveryListener getDiscoveryListener() { return LauncherDiscoveryListener.NOOP; } - NamespacedHierarchicalStore getStore(); - } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultDiscoveryRequest.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultDiscoveryRequest.java index 7621225c7e58..6bd73c4b641e 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultDiscoveryRequest.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultDiscoveryRequest.java @@ -20,9 +20,7 @@ import org.junit.platform.engine.DiscoveryFilter; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.EngineDiscoveryRequest; -import org.junit.platform.engine.Namespace; import org.junit.platform.engine.reporting.OutputDirectoryProvider; -import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.EngineFilter; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; @@ -56,17 +54,14 @@ final class DefaultDiscoveryRequest implements LauncherDiscoveryRequest { private final OutputDirectoryProvider outputDirectoryProvider; - private final NamespacedHierarchicalStore store; - DefaultDiscoveryRequest(List selectors, List engineFilters, List> discoveryFilters, List postDiscoveryFilters, - NamespacedHierarchicalStore store, LauncherConfigurationParameters configurationParameters, - LauncherDiscoveryListener discoveryListener, OutputDirectoryProvider outputDirectoryProvider) { + LauncherConfigurationParameters configurationParameters, LauncherDiscoveryListener discoveryListener, + OutputDirectoryProvider outputDirectoryProvider) { this.selectors = selectors; this.engineFilters = engineFilters; this.discoveryFilters = discoveryFilters; this.postDiscoveryFilters = postDiscoveryFilters; - this.store = store; this.configurationParameters = configurationParameters; this.discoveryListener = discoveryListener; this.outputDirectoryProvider = outputDirectoryProvider; @@ -104,11 +99,6 @@ public LauncherDiscoveryListener getDiscoveryListener() { return this.discoveryListener; } - @Override - public NamespacedHierarchicalStore getStore() { - return this.store; - } - @Override public OutputDirectoryProvider getOutputDirectoryProvider() { return this.outputDirectoryProvider; diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java index df39f9d27164..cbdc9023896d 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java @@ -102,6 +102,11 @@ public void execute(TestPlan testPlan, TestExecutionListener... listeners) { execute((InternalTestPlan) testPlan, listeners); } + @Override + public NamespacedHierarchicalStore getStore(LauncherDiscoveryRequest launcherDiscoveryRequest) { + return new NamespacedHierarchicalStore<>(this.sessionStore); + } + private LauncherDiscoveryResult discover(LauncherDiscoveryRequest discoveryRequest, EngineDiscoveryOrchestrator.Phase phase) { return discoveryOrchestrator.discover(discoveryRequest, phase); diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java index c2b54bed66dc..09df7277989e 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java @@ -118,6 +118,11 @@ public void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, TestExecu public void execute(TestPlan testPlan, TestExecutionListener... listeners) { throw new PreconditionViolationException("Launcher session has already been closed"); } + + @Override + public NamespacedHierarchicalStore getStore(LauncherDiscoveryRequest launcherDiscoveryRequest) { + throw new PreconditionViolationException("Launcher session has already been closed"); + } } private static LauncherInterceptor composite(List interceptors) { diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncher.java index 3cc6be2316f0..297f85e02ee2 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncher.java @@ -10,6 +10,8 @@ package org.junit.platform.launcher.core; +import org.junit.platform.engine.Namespace; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; @@ -52,4 +54,9 @@ public void execute(TestPlan testPlan, TestExecutionListener... listeners) { delegate.execute(testPlan, listeners); } + @Override + public NamespacedHierarchicalStore getStore(LauncherDiscoveryRequest launcherDiscoveryRequest) { + return delegate.getStore(launcherDiscoveryRequest); + } + } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java index 1775fd5e8e80..5f6e9f5fe119 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java @@ -29,9 +29,7 @@ import org.junit.platform.engine.DiscoveryFilter; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.Filter; -import org.junit.platform.engine.Namespace; import org.junit.platform.engine.reporting.OutputDirectoryProvider; -import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.EngineFilter; import org.junit.platform.launcher.LauncherConstants; import org.junit.platform.launcher.LauncherDiscoveryListener; @@ -112,8 +110,6 @@ public final class LauncherDiscoveryRequestBuilder { private final Map configurationParameters = new HashMap<>(); private final List configurationParametersResources = new ArrayList<>(); private final List discoveryListeners = new ArrayList<>(); - // TODO[#4252] Use the session-level store as its parent. - private final NamespacedHierarchicalStore store = new NamespacedHierarchicalStore<>(null); private boolean implicitConfigurationParametersEnabled = true; private ConfigurationParameters parentConfigurationParameters; private OutputDirectoryProvider outputDirectoryProvider; @@ -340,8 +336,7 @@ public LauncherDiscoveryRequest build() { LauncherDiscoveryListener discoveryListener = getLauncherDiscoveryListener(launcherConfigurationParameters); OutputDirectoryProvider outputDirectoryProvider = getOutputDirectoryProvider(launcherConfigurationParameters); return new DefaultDiscoveryRequest(this.selectors, this.engineFilters, this.discoveryFilters, - this.postDiscoveryFilters, this.store, launcherConfigurationParameters, discoveryListener, - outputDirectoryProvider); + this.postDiscoveryFilters, launcherConfigurationParameters, discoveryListener, outputDirectoryProvider); } private OutputDirectoryProvider getOutputDirectoryProvider( diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java index dffde014867a..1aaa71af400a 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java @@ -13,6 +13,8 @@ import java.util.List; import java.util.function.Supplier; +import org.junit.platform.engine.Namespace; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; @@ -71,6 +73,13 @@ public void execute(TestPlan testPlan, TestExecutionListener... listeners) { } } + @Override + public NamespacedHierarchicalStore getStore(LauncherDiscoveryRequest launcherDiscoveryRequest) { + try (LauncherSession session = createSession()) { + return session.getLauncher().getStore(launcherDiscoveryRequest); + } + } + private LauncherSession createSession() { LauncherSession session = new DefaultLauncherSession(interceptorFactory.get(), sessionListenerSupplier, launcherSupplier);