Skip to content

Commit

Permalink
Minor chgs wrt Jupiter: Extension_Mats handling @nested, and determin…
Browse files Browse the repository at this point in the history
…ing test instances

* Extension_Mats: Use ExtensionContext to determine if we are in a
nested class: Go up the ExtensionContext hierachy to find Extension_Mats
* Extension_MatsAnnotatedClass: Use ExtensionContext to find test
instances, both @nested and enclosing test class instances.
* Changed init for Extension_Mats a tad: Using context.getRoot()
directly.

(Original changes by Ståle Undheim)
  • Loading branch information
stolsvik committed Feb 10, 2025
1 parent 1a5a491 commit 6188cc7
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.mats3.impl.jms.JmsMatsFactory;
import io.mats3.serial.MatsSerializer;
Expand Down Expand Up @@ -51,11 +53,11 @@
* }
* </pre>
*
*
* @author Endre Stølsvik - 2015 - http://endre.stolsvik.com
* @author Kevin Mc Tiernan, 2020-10-18, [email protected]
*/
public class Rule_Mats extends AbstractMatsTest implements TestRule {
protected static final Logger log = LoggerFactory.getLogger(Rule_Mats.class);

protected Rule_Mats(MatsSerializer<?> matsSerializer) {
super(matsSerializer);
Expand Down Expand Up @@ -107,12 +109,16 @@ public Statement apply(Statement base, Description description) {
}
return new Statement() {
public void evaluate() throws Throwable {
log.info(LOG_PREFIX+"INIT: beforeAll on JUnit @ClassRule " + idThis() + ".");
beforeAll();
log.info(LOG_PREFIX+"-- init done: beforeAll on JUnit @ClassRule " + idThis() + ".");
try {
base.evaluate();
}
finally {
log.info(LOG_PREFIX+"CLEANUP: afterAll on JUnit @ClassRule " + idThis() + ".");
afterAll();
log.info(LOG_PREFIX+"-- cleanup done: afterAll on JUnit @ClassRule " + idThis() + ".");
}
}
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.mats3.test.junit;

import java.util.Collections;

import org.junit.rules.MethodRule;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.Statement;
Expand Down Expand Up @@ -119,7 +121,7 @@ public Statement apply(Statement base, FrameworkMethod method, Object target) {
+ "target [" + target + "], base [" + base + "]");
return new Statement() {
public void evaluate() throws Throwable {
beforeEach(target);
beforeEach(Collections.singletonList(target));
try {
base.evaluate();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ public class U_RuleMatsTest {

@Rule
public final Rule_MatsEndpoint<String, String> _helloEndpoint = Rule_MatsEndpoint
.create(MATS, "HelloEndpoint", String.class, String.class).setProcessLambda((ctx, msg) -> "Hello " + msg);
.create(MATS, "HelloEndpoint", String.class, String.class)
.setProcessLambda((ctx, msg) -> "Hello " + msg);

@Rule
public final Rule_MatsEndpoint<Void, String> _terminator = Rule_MatsEndpoint
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
package io.mats3.test.jupiter;

import java.util.concurrent.atomic.AtomicInteger;

import javax.sql.DataSource;

import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;

import io.mats3.MatsFactory;
import io.mats3.MatsInitiator;
import io.mats3.impl.jms.JmsMatsFactory;
import io.mats3.serial.MatsSerializer;
import io.mats3.serial.json.MatsSerializerJson;
import io.mats3.test.MatsTestBrokerInterface;
import io.mats3.test.TestH2DataSource;
import io.mats3.test.abstractunit.AbstractMatsTest;
import io.mats3.util.MatsFuturizer;

/**
* Provides a full MATS harness for unit testing by creating {@link JmsMatsFactory MatsFactory} utilizing an in-vm
Expand Down Expand Up @@ -101,8 +95,6 @@ public static Extension_Mats createWithDb(MatsSerializer<?> matsSerializer) {
return new Extension_Mats(matsSerializer, testH2DataSource);
}

private final AtomicInteger _nestinglevel = new AtomicInteger(0);

/**
* Returns the {@link Extension_Mats} from the test context, provided that this has been initialized prior to
* calling this method. This is intended for use by other extensions that rely on the presence of a
Expand All @@ -111,59 +103,109 @@ public static Extension_Mats createWithDb(MatsSerializer<?> matsSerializer) {
* <p>
* Note that if you crate multiple {@link Extension_Mats}, then this will only provide the last created extension.
* In that case, you should instead provide the actual MatsFactory to each extension.
* <p>
* In a scenario with nested classes, we only register the Extension_Mats in the top level class, where the
* extension has been added with {@link org.junit.jupiter.api.extension.RegisterExtension}.
*
* @param extensionContext
* to get {@link Extension_Mats} from
* @return the {@link Extension_Mats} from the test context
* @throws IllegalStateException
* if no {@link Extension_Mats} is found in the test context
*/
public static Extension_Mats getExtension(ExtensionContext extensionContext) {
Extension_Mats extensionMats = extensionContext.getStore(NAMESPACE).get(Extension_Mats.class,
Extension_Mats.class);
if (extensionMats == null) {
throw new IllegalStateException("Could not find Extension_Mats in ExtensionContext,"
+ " make sure to include Extension_Mats as a test extension.");
public static Extension_Mats findFromContext(ExtensionContext extensionContext) {
// :: Get it from Root context, if present.
Extension_Mats mats = extensionContext.getRoot().getStore(NAMESPACE)
.get(Extension_Mats.class, Extension_Mats.class);
// ?: Did we find it in root?
if (mats == null) {
// -> No, we did not find it in root - throw exception
throw new IllegalStateException("Could not find Extension_Mats in ExtensionContext, make sure to include"
+ " Extension_Mats as a test extension.");
}
return extensionMats;

// E-> Yes, we found it in root, return it.
return mats;
}

/**
* Executed by Jupiter before any test method is executed. (Once at the start of the class.)
*/
@Override
public void beforeAll(ExtensionContext context) {
context.getStore(NAMESPACE).put(Extension_Mats.class, this);
// Handle the "nesting level'ing" of the beforeAll/afterAll
int nestingLevel = _nestinglevel.getAndIncrement();
// ?: Are we at the top level?
if (nestingLevel != 0) {
// -> No not at top, so then we ignore the beforeAll
log.debug("+++ Jupiter +++ beforeAll(..) invoked, but ignoring since nesting level is > 0: "
+ nestingLevel);
}
else {
// -> Yes, we are at the top level, so then we do the beforeAll
super.beforeAll();
if (log.isDebugEnabled()) log.debug(LOG_PREFIX + "beforeAll: Context chain to root: "
+ getContextsToRoot(context));

// Get root Extension context.
// Note: The 0th level of tests actually still have a parent, which is the "engine" level.
// This seems to be the root.
ExtensionContext root = context.getRoot();

// ?: Check if the Extension_Mats is already registered in the root context
if (root.getStore(NAMESPACE).get(Extension_Mats.class, Extension_Mats.class) != null) {
// -> Already present: We do not need to do anything
log.info(LOG_PREFIX + "INIT SKIPPED: beforeAll on Jupiter @Extension " + idThis() + " for "
+ id(context) + " - skipping since the root context already has Extension_Mats present.");
return;
}

// E-> We do not have an Extension_Mats in the root context, so we do the init of this Extension_Mats.
log.info(LOG_PREFIX + "INIT: beforeAll on Jupiter @Extension " + idThis() + " for " + id(context)
+ ", root is " + id(root) + " - setting up MQ Broker and JMS MatsFactory.");
// Put in root context
root.getStore(NAMESPACE).put(Extension_Mats.class, this);
// Store that it was this ExtensionContext that put it in root context (Needed for removal)
root.getStore(NAMESPACE).put(Extension_Mats.class.getName() + ".contextAdder", context);
super.beforeAll();
log.info(LOG_PREFIX + "-- init done: beforeAll on Jupiter @Extension " + idThis() + ", put it in root context "
+ id(root) + ".");
}

/**
* Executed by Jupiter after all test methods have been executed. (Once at the end of the class.)
*/
@Override
public void afterAll(ExtensionContext context) {
// Handle the "nesting level'ing" of the beforeAll/afterAll
int nestingLevel = _nestinglevel.decrementAndGet();
// ?: Are we at the top level?
if (nestingLevel != 0) {
// -> No not at top, so then we ignore the afterAll
log.debug("--- Jupiter --- afterAll(..) invoked, but ignoring since nesting level is > 0: "
+ nestingLevel);
if (log.isDebugEnabled()) log.debug(LOG_PREFIX + "afterAll: Context chain to root: "
+ getContextsToRoot(context));

// Get root Extension context
ExtensionContext root = context.getRoot();

// Get which ExtensionContext (i.e. "level") that put the Extension_Mats in the root context
ExtensionContext contextThatPutItInRoot = root.getStore(NAMESPACE).get(Extension_Mats.class.getName()
+ ".contextAdder", ExtensionContext.class);

// ?: Was it the current ExtensionContext that put the Extension_Mats in the root context?
if (contextThatPutItInRoot != context) {
// -> No, it was not us, so skip the cleanup.
log.info(LOG_PREFIX + "CLEANUP SKIPPED: afterAll on Jupiter @Extension " + idThis() + " for "
+ id(context) + " - this level didn't initialize it.");
return;
}
else {
// -> Yes, we are at the top level, so then we do the afterAll
super.afterAll();

// E-> Yes, we are the root context, so we do the afterAll
log.info(LOG_PREFIX + "CLEANUP: afterAll on Jupiter @Extension " + idThis() + " for " + id(context)
+ ", root is " + id(root) + " - taking down JMS MatsFactory and MQ Broker.");
super.afterAll();
root.getStore(NAMESPACE).remove(Extension_Mats.class);
log.info(LOG_PREFIX + "-- cleanup done: afterAll on Jupiter @Extension " + idThis() + ".");
}

// Contexts to root, but using a StringBuffer for the output, and SimpleName
private String getContextsToRoot(ExtensionContext context) {
StringBuilder sb = new StringBuilder();
boolean first = true;
while (context != null) {
if (first) {
first = false;
}
else {
sb.append(" -> ");
}
sb.append(id(context));
context = context.getParent().orElse(null);
}
return sb.toString();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package io.mats3.test.jupiter;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
Expand Down Expand Up @@ -102,7 +107,11 @@ public Extension_MatsAnnotatedClass withAnnotatedMatsInstances(Object... annotat

@Override
public void beforeEach(ExtensionContext extensionContext) {
beforeEach(extensionContext.getTestInstance().orElse(null));
List<Object> allInstances = new ArrayList<>(extensionContext.getRequiredTestInstances().getAllInstances());
// By default, the instance order is from the root to the leaf. We instead want the leaf first, so that
// fields there take precedence over fields higher up in the hierarchy.
Collections.reverse(allInstances);
beforeEach(allInstances);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ public static <R, I> Extension_MatsEndpoint<R, I> create(Extension_Mats matsRule
@Override
public void beforeEach(ExtensionContext context) {
if (_matsFactory == null) {
_matsFactory = Extension_Mats.getExtension(context).getMatsFactory();
_matsFactory = Extension_Mats.findFromContext(context).getMatsFactory();
}
super.before();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,6 @@ class TestAnnotatedWithProvidedClass {
.create(MATS)
.withAnnotatedMatsClasses(AnnotatedMats3Endpoint.class);

// This is evidently needed for some runs on Java 21: It seems to sometimes elide the synthetic field
// representing the outer/enclosing class instance when there are no references to it, which there ain't in
// this test class.
private ServiceDependency _serviceDependency = J_ExtensionMatsAnnotatedClassBasicsTest.this._serviceDependency;

@Test
void testAnnotatedMatsClass() throws ExecutionException, InterruptedException, TimeoutException {
// :: Setup
Expand Down Expand Up @@ -248,7 +243,7 @@ void testDuplicateRegistration() {
// This will of course change if this file changes. But just look at the line number in your editor for
// the first call to withAnnotatedMatsInstances (the 2nd call will be present in the stacktrace)
boolean exceptionAsExpected = assertionError.getMessage()
.contains("J_ExtensionMatsAnnotatedClassBasicsTest.java:240");
.contains("J_ExtensionMatsAnnotatedClassBasicsTest.java:235");
if (!exceptionAsExpected) {
throw new AssertionError("The exception message did not contain the expected content!"
+ " (including line number of expected double registration! Did you change the test class?"
Expand Down
Loading

0 comments on commit 6188cc7

Please sign in to comment.