diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.7.0.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.7.0.adoc
index c56d47d3b0cf..845904fa6713 100644
--- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.7.0.adoc
+++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.7.0.adoc
@@ -31,7 +31,8 @@ on GitHub.
==== Bug Fixes
-* ❓
+* `CloseableResource` instances stored in `ExtensionContext.Store` are now closed in the
+ reverse order they were added in. Previously, the order was undefined and unstable.
==== Deprecations and Breaking Changes
diff --git a/documentation/src/docs/asciidoc/user-guide/extensions.adoc b/documentation/src/docs/asciidoc/user-guide/extensions.adoc
index c02b91281daa..3ad1310f8a26 100644
--- a/documentation/src/docs/asciidoc/user-guide/extensions.adoc
+++ b/documentation/src/docs/asciidoc/user-guide/extensions.adoc
@@ -569,8 +569,8 @@ methods available for storing and retrieving values via the `{ExtensionContext_S
.`ExtensionContext.Store.CloseableResource`
NOTE: An extension context store is bound to its extension context lifecycle. When an
extension context lifecycle ends it closes its associated store. All stored values
-that are instances of `CloseableResource` are notified by
-an invocation of their `close()` method.
+that are instances of `CloseableResource` are notified by an invocation of their `close()`
+method in the inverse order they were added in.
[[extensions-supported-utilities]]
=== Supported Utilities in Extensions
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 dc8295f7ffb1..6a244f950e54 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
@@ -396,6 +396,9 @@ interface Store {
*
Note that the {@code CloseableResource} API is only honored for
* objects stored within an extension context {@link Store Store}.
*
+ *
The resources stored in a {@link Store Store} are closed in the
+ * inverse order they were added in.
+ *
* @since 5.1
*/
@API(status = STABLE, since = "5.1")
diff --git a/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/extension/ExtensionContextParameterResolver.java b/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/extension/ExtensionContextParameterResolver.java
new file mode 100644
index 000000000000..4c6df517335c
--- /dev/null
+++ b/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/extension/ExtensionContextParameterResolver.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2015-2020 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.extension;
+
+public class ExtensionContextParameterResolver implements ParameterResolver {
+
+ @Override
+ public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
+ throws ParameterResolutionException {
+ return ExtensionContext.class.equals(parameterContext.getParameter().getType());
+ }
+
+ @Override
+ public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
+ throws ParameterResolutionException {
+ return extensionContext;
+ }
+}
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ExtensionValuesStore.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ExtensionValuesStore.java
index 66a12d5e4f4e..b7df116a62f4 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ExtensionValuesStore.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ExtensionValuesStore.java
@@ -15,10 +15,12 @@
import static org.junit.platform.commons.util.ReflectionUtils.getWrapperType;
import static org.junit.platform.commons.util.ReflectionUtils.isAssignableTo;
+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;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
@@ -27,6 +29,7 @@
import org.apiguardian.api.API;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
+import org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource;
import org.junit.jupiter.api.extension.ExtensionContextException;
import org.junit.platform.engine.support.hierarchical.ThrowableCollector;
@@ -39,34 +42,36 @@
@API(status = INTERNAL, since = "5.0")
public class ExtensionValuesStore {
+ private static final Comparator REVERSE_INSERT_ORDER = Comparator. comparing(
+ it -> it.order).reversed();
+
+ private final AtomicInteger insertOrderSequence = new AtomicInteger();
+ private final ConcurrentMap storedValues = new ConcurrentHashMap<>(4);
private final ExtensionValuesStore parentStore;
- private final ConcurrentMap> storedValues = new ConcurrentHashMap<>(4);
public ExtensionValuesStore(ExtensionValuesStore parentStore) {
this.parentStore = parentStore;
}
/**
- * Close all values that implement {@link ExtensionContext.Store.CloseableResource}.
+ * Close all values that implement {@link CloseableResource}.
*
* @implNote Only close values stored in this instance. This implementation
* does not close values in parent stores.
*/
public void closeAllStoredCloseableValues() {
ThrowableCollector throwableCollector = createThrowableCollector();
- for (Supplier