Skip to content

Commit

Permalink
Follow to #503 for Plugin scope to match the test bean scope
Browse files Browse the repository at this point in the history
- Rename GlobalTestScope -> GlobalTestBeans
- Lift and rename GlobalTestScope.Pair to TestBeans
- The plugin scope lives in TestBeans and scoped with the associated test bean scope (created together, closed together)
- Rename PluginInitialise to PluginMgr
- Rename TSBuild to GlobalInitialise

Note the plugin for avaje-nima creates the nima http server, so now this will be started and stopped with the associated bean scope.
  • Loading branch information
rbygrave committed Feb 19, 2024
1 parent 0acfb27 commit 157c0c5
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@
* Takes into when Service loading does not work such as when using module-path.
* In that case loads the META-INF services resources and uses reflection.
*/
final class TSBuild {
final class GlobalInitialise {

private static final ReentrantLock lock = new ReentrantLock();
private static GlobalTestScope.Pair SCOPE;
private static GlobalTestBeans.Beans SCOPE;

private final boolean shutdownHook;

Expand All @@ -31,13 +31,13 @@ final class TSBuild {
*/
@Nullable
static BeanScope createTestBaseScope(boolean shutdownHook) {
return new TSBuild(shutdownHook).build();
return new GlobalInitialise(shutdownHook).build();
}

/**
* Return the test BeanScope only creating once.
*/
static GlobalTestScope.Pair initialise(boolean shutdownHook) {
static GlobalTestBeans.Beans initialise(boolean shutdownHook) {
lock.lock();
try {
if (SCOPE == null) {
Expand All @@ -49,14 +49,15 @@ static GlobalTestScope.Pair initialise(boolean shutdownHook) {
}
}

TSBuild(boolean shutdownHook) {
GlobalInitialise(boolean shutdownHook) {
this.shutdownHook = shutdownHook;
}

private static GlobalTestScope.Pair createScopes(boolean shutdownHook) {
private static GlobalTestBeans.Beans createScopes(boolean shutdownHook) {
BeanScope testBaseScope = createTestBaseScope(shutdownHook);
BeanScope testAllScope = createTestAllScope(testBaseScope);
return new GlobalTestScope.Pair(testAllScope, testBaseScope);
Plugin.Scope pluginAll = PluginMgr.scope(testAllScope);
return new GlobalTestBeans.Beans(pluginAll, testAllScope, testBaseScope);
}

private static BeanScope createTestAllScope(BeanScope testBaseScope) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,30 @@
/**
* Holds the global BeanScope used for all tests.
*/
final class GlobalTestScope implements ExtensionContext.Store.CloseableResource {
final class GlobalTestBeans implements ExtensionContext.Store.CloseableResource {

private static final System.Logger log = AppLog.getLogger("io.avaje.inject");

private final ReentrantLock lock = new ReentrantLock();
private boolean started;
private Pair globalBeanScope;
private Beans globalBeans;

Pair obtain(ExtensionContext context) {
Beans obtain(ExtensionContext context) {
lock.lock();
try {
if (!started) {
initialise(context);
started = true;
}
return globalBeanScope;
return globalBeans;
} finally {
lock.unlock();
}
}

private void initialise(ExtensionContext context) {
globalBeanScope = TSBuild.initialise(false);
log.log(TRACE, "register global test BeanScope with beans {0}", globalBeanScope);
globalBeans = GlobalInitialise.initialise(false);
log.log(TRACE, "register global test BeanScope with beans {0}", globalBeans);
context.getRoot().getStore(ExtensionContext.Namespace.GLOBAL).put(InjectExtension.class.getCanonicalName(), this);
}

Expand All @@ -46,64 +46,73 @@ private void initialise(ExtensionContext context) {
public void close() {
lock.lock();
try {
if (globalBeanScope != null) {
if (globalBeans != null) {
log.log(DEBUG, "Closing global test BeanScope");
globalBeanScope.close();
globalBeans.close();
}
} finally {
lock.unlock();
}
}

/**
* The pair of BeanScopes that can be used for InjectTests.
* The BeanScopes and plugin scope that can be used for InjectTests.
*/
static final class Pair {
static final class Beans {

private final Plugin.Scope plugin;

/**
* Entire application wired (with testScope as parent replacing those beans).
* This can be used when a test only injects beans and there are no mocks,
* spies, or setup methods.
*/
private final BeanScope allScope;
private final BeanScope allBeans;

/**
* The TestScope beans, used as the parent scope when a new BeanScope
* needs to be wired for a test (due to mocks, spies or setup methods).
*/
private final BeanScope baseScope;
private final BeanScope baseBeans;

Pair(BeanScope allScope, BeanScope baseScope) {
this.allScope = allScope;
this.baseScope = baseScope;
Beans(Plugin.Scope plugin, BeanScope allBeans, BeanScope baseBeans) {
this.plugin = plugin;
this.allBeans = allBeans;
this.baseBeans = baseBeans;
}

void close() {
if (allScope != null) {
allScope.close();
if (plugin != null) {
plugin.close();
}
if (allBeans != null) {
allBeans.close();
}
if (baseScope != null) {
baseScope.close();
if (baseBeans != null) {
baseBeans.close();
}
}

BeanScope allScope() {
return allScope;
Plugin.Scope allPlugin() {
return plugin;
}

BeanScope baseScope() {
return baseScope;
BeanScope allBeans() {
return allBeans;
}

Pair newPair(BeanScope newAllScope) {
return new Pair(newAllScope, baseScope);
BeanScope baseBeans() {
return baseBeans;
}

Beans withBeans(TestBeans otherBeans) {
return new Beans(otherBeans.plugin(), otherBeans.beanScope(), baseBeans);
}

@Override
public String toString() {
return "All[" + allScope + "] Test[" + baseScope + "]";
return "All[" + allBeans + "] Test[" + baseBeans + "]";
}

}

}
28 changes: 13 additions & 15 deletions inject-test/src/main/java/io/avaje/inject/test/InjectExtension.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package io.avaje.inject.test;

import io.avaje.applog.AppLog;
import io.avaje.inject.BeanScope;
import org.junit.jupiter.api.extension.*;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;

Expand All @@ -19,18 +18,18 @@ public final class InjectExtension implements BeforeAllCallback, AfterAllCallbac
private static final Namespace INJECT_NS = Namespace.create("io.avaje.inject.InjectTest");
private static final String BEAN_SCOPE = "BEAN_SCOPE";
private static final String META = "META";
private static final GlobalTestScope GLOBAL = new GlobalTestScope();
private static final GlobalTestBeans GLOBAL = new GlobalTestBeans();

private GlobalTestScope.Pair globalBeanScope;
private GlobalTestBeans.Beans globalTestBeans;

@Override
public void beforeAll(ExtensionContext context) {
globalBeanScope = GLOBAL.obtain(context);
globalTestBeans = GLOBAL.obtain(context);

final MetaInfo metaInfo = createMetaInfo(context);
putMetaInfo(context, metaInfo);
if (metaInfo.hasStaticInjection()) {
putClassScope(context, metaInfo.buildForClass(globalBeanScope));
putClassScope(context, metaInfo.buildForClass(globalTestBeans));
}
}

Expand All @@ -44,15 +43,15 @@ private MetaInfo getMetaInfo(ExtensionContext context) {
return (MetaInfo) context.getStore(INJECT_NS).get(META + testClass);
}

private void putClassScope(ExtensionContext context, MetaInfo.Scope testClassBeanScope) {
private void putClassScope(ExtensionContext context, TestBeans testClassBeanScope) {
Class<?> testClass = context.getRequiredTestClass();
context.getStore(INJECT_NS).put(BEAN_SCOPE + testClass, testClassBeanScope);
}

private BeanScope getClassScope(ExtensionContext context) {
private GlobalTestBeans.Beans getClassScope(ExtensionContext context, GlobalTestBeans.Beans globalTestBeans) {
Class<?> testClass = context.getRequiredTestClass();
MetaInfo.Scope pair = (MetaInfo.Scope) context.getStore(INJECT_NS).get(BEAN_SCOPE + testClass);
return pair.beanScope();
TestBeans testBeans = (TestBeans) context.getStore(INJECT_NS).get(BEAN_SCOPE + testClass);
return globalTestBeans.withBeans(testBeans);
}

/**
Expand All @@ -62,14 +61,13 @@ private BeanScope getClassScope(ExtensionContext context) {
public void beforeEach(final ExtensionContext context) {
final MetaInfo metaInfo = getMetaInfo(context);
if (metaInfo.hasInstanceInjection()) {

// if (static fields) then (class scope) else (globalTestScope)
final GlobalTestScope.Pair pair = metaInfo.hasStaticInjection() ? globalBeanScope.newPair(getClassScope(context)) : globalBeanScope;
AutoCloseable beanScope = metaInfo.buildForInstance(pair, context.getRequiredTestInstance());
// if (static fields) then (class scope) else (global scope)
final GlobalTestBeans.Beans parentBeans = metaInfo.hasStaticInjection() ? getClassScope(context, globalTestBeans) : globalTestBeans;
AutoCloseable metaScope = metaInfo.buildForInstance(parentBeans, context.getRequiredTestInstance());

// put method level test scope
Method testMethod = context.getRequiredTestMethod();
context.getStore(INJECT_NS).put(BEAN_SCOPE + testMethod, beanScope);
context.getStore(INJECT_NS).put(BEAN_SCOPE + testMethod, metaScope);
}
}

Expand Down Expand Up @@ -109,7 +107,7 @@ public void afterAll(ExtensionContext context) {
* Return the MetaInfo.
*/
private MetaInfo createMetaInfo(ExtensionContext context) {
return new MetaInfo(context.getRequiredTestClass(), PluginInitialise.plugin());
return new MetaInfo(context.getRequiredTestClass(), PluginMgr.plugin());
}

}
67 changes: 20 additions & 47 deletions inject-test/src/main/java/io/avaje/inject/test/MetaInfo.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package io.avaje.inject.test;

import java.util.Optional;

import io.avaje.inject.BeanScope;
import io.avaje.inject.BeanScopeBuilder;

import java.util.Optional;

/**
* Wraps the underlying metadata (fields with annotations @Mock, @Spy, @Inject, @Captor).
*/
Expand All @@ -27,78 +27,51 @@ boolean hasInstanceInjection() {
/**
* Build for static fields class level scope.
*/
Scope buildForClass(GlobalTestScope.Pair globalTestScope) {
TestBeans buildForClass(GlobalTestBeans.Beans globalTestScope) {
return buildSet(globalTestScope, null);
}

/**
* Build test instance per test scope.
*/
Scope buildForInstance(GlobalTestScope.Pair globalTestScope, Object testInstance) {
TestBeans buildForInstance(GlobalTestBeans.Beans globalTestScope, Object testInstance) {
return buildSet(globalTestScope, testInstance);
}

private Scope buildSet(GlobalTestScope.Pair parent, Object testInstance) {
private TestBeans buildSet(GlobalTestBeans.Beans parent, Object testInstance) {
var testBeans = buildTestBeans(parent, testInstance);
// set inject, spy, mock fields from beanScope
return reader.setFromScope(testBeans, testInstance);
}

private TestBeans buildTestBeans(GlobalTestBeans.Beans parent, Object testInstance) {
// wiring profiles
String[] profiles = Optional.ofNullable(testInstance)
.map(Object::getClass)
.map(c -> c.getAnnotation(InjectTest.class))
.map(InjectTest::profiles)
.orElse(new String[0]);

boolean newScope = false;
final BeanScope beanScope;
if (profiles.length > 0 || reader.hasMocksOrSpies(testInstance)) {
// need to build a BeanScope for this using testScope() as the parent
// need to build a BeanScope for this using baseBeans() as the parent
final BeanScopeBuilder builder = BeanScope.builder();
if (parent != null) {
builder.parent(parent.baseScope(), false);
builder.parent(parent.baseBeans(), false);
if (profiles.length > 0) {
builder.profiles(profiles);
}
}
// register mocks and spies local to this test
reader.build(builder, testInstance);
// wire with local mocks, spies, and globalTestScope
beanScope = builder.build();
newScope = true;
// wire with local mocks, spies, and TestScope beans
var newBeanScope = builder.build();
var newPlugin = PluginMgr.scope(newBeanScope);
return new TestBeans(newBeanScope, newPlugin);

} else {
// just use the all scope
beanScope = parent.allScope();
// just use the existing beans and plugin from parent
return new TestBeans(parent);
}

// set inject, spy, mock fields from beanScope
return reader.setFromScope(beanScope, testInstance, newScope);
}

/**
* Wraps both BeanScope and Plugin.Scope for either EACH or ALL
* (aka instance or class level).
*/
static class Scope implements AutoCloseable {

private final BeanScope beanScope;
private final Plugin.Scope pluginScope;
private final boolean newScope;

Scope(BeanScope beanScope, Plugin.Scope pluginScope, boolean newScope) {
this.beanScope = beanScope;
this.pluginScope = pluginScope;
this.newScope = newScope;
}

BeanScope beanScope() {
return beanScope;
}

@Override
public void close() {
if (newScope) {
beanScope.close();
}
if (pluginScope != null) {
pluginScope.close();
}
}
}
}
Loading

0 comments on commit 157c0c5

Please sign in to comment.