Add initial tests for the framework registry (#31433)
Closes #31432 Signed-off-by: stianst <stianst@gmail.com>
This commit is contained in:
parent
6afc8c3e04
commit
02e3339762
12 changed files with 367 additions and 6 deletions
|
@ -35,7 +35,7 @@ public class KeycloakIntegrationTestExtension implements BeforeEachCallback, Aft
|
||||||
|
|
||||||
private Registry getRegistry(ExtensionContext context) {
|
private Registry getRegistry(ExtensionContext context) {
|
||||||
ExtensionContext.Store store = getStore(context);
|
ExtensionContext.Store store = getStore(context);
|
||||||
Registry registry = (Registry) store.getOrComputeIfAbsent(Registry.class, r -> new Registry());
|
Registry registry = (Registry) store.getOrComputeIfAbsent(Registry.class, r -> createRegistry());
|
||||||
registry.setCurrentContext(context);
|
registry.setCurrentContext(context);
|
||||||
return registry;
|
return registry;
|
||||||
}
|
}
|
||||||
|
@ -47,4 +47,10 @@ public class KeycloakIntegrationTestExtension implements BeforeEachCallback, Aft
|
||||||
return context.getStore(ExtensionContext.Namespace.create(getClass()));
|
return context.getStore(ExtensionContext.Namespace.create(getClass()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Registry createRegistry() {
|
||||||
|
Registry registry = new Registry();
|
||||||
|
Runtime.getRuntime().addShutdownHook(new Thread(registry::close));
|
||||||
|
return registry;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,6 @@ public class Registry {
|
||||||
|
|
||||||
public Registry() {
|
public Registry() {
|
||||||
loadSuppliers();
|
loadSuppliers();
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(this::onShutdown));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExtensionContext getCurrentContext() {
|
public ExtensionContext getCurrentContext() {
|
||||||
|
@ -99,7 +98,9 @@ public class Registry {
|
||||||
private void findRequestedInstances(Object testInstance) {
|
private void findRequestedInstances(Object testInstance) {
|
||||||
Class testClass = testInstance.getClass();
|
Class testClass = testInstance.getClass();
|
||||||
RequestedInstance requestedServerInstance = createRequestedInstance(testClass.getAnnotations(), null);
|
RequestedInstance requestedServerInstance = createRequestedInstance(testClass.getAnnotations(), null);
|
||||||
|
if (requestedServerInstance != null) {
|
||||||
requestedInstances.add(requestedServerInstance);
|
requestedInstances.add(requestedServerInstance);
|
||||||
|
}
|
||||||
|
|
||||||
for (Field f : testClass.getDeclaredFields()) {
|
for (Field f : testClass.getDeclaredFields()) {
|
||||||
RequestedInstance requestedInstance = createRequestedInstance(f.getAnnotations(), f.getType());
|
RequestedInstance requestedInstance = createRequestedInstance(f.getAnnotations(), f.getType());
|
||||||
|
@ -182,9 +183,9 @@ public class Registry {
|
||||||
destroy.forEach(this::destroy);
|
destroy.forEach(this::destroy);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onShutdown() {
|
public void close() {
|
||||||
LOGGER.trace("Closing instances with global lifecycle");
|
LOGGER.trace("Closing all instances");
|
||||||
List<InstanceContext<?, ?>> destroy = deployedInstances.stream().filter(i -> i.getLifeCycle().equals(LifeCycle.GLOBAL)).toList();
|
List<InstanceContext<?, ?>> destroy = deployedInstances.stream().toList();
|
||||||
destroy.forEach(this::destroy);
|
destroy.forEach(this::destroy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,153 @@
|
||||||
|
package org.keycloak.test.framework.injection;
|
||||||
|
|
||||||
|
import org.hamcrest.MatcherAssert;
|
||||||
|
import org.hamcrest.Matchers;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.keycloak.test.framework.injection.mocks.MockChildAnnotation;
|
||||||
|
import org.keycloak.test.framework.injection.mocks.MockChildSupplier;
|
||||||
|
import org.keycloak.test.framework.injection.mocks.MockChildValue;
|
||||||
|
import org.keycloak.test.framework.injection.mocks.MockInstances;
|
||||||
|
import org.keycloak.test.framework.injection.mocks.MockParentAnnotation;
|
||||||
|
import org.keycloak.test.framework.injection.mocks.MockParentSupplier;
|
||||||
|
import org.keycloak.test.framework.injection.mocks.MockParentValue;
|
||||||
|
|
||||||
|
public class RegistryTest {
|
||||||
|
|
||||||
|
private Registry registry;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void resetMocks() {
|
||||||
|
MockInstances.reset();
|
||||||
|
MockParentSupplier.reset();
|
||||||
|
MockChildSupplier.reset();
|
||||||
|
registry = new Registry();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGlobalLifeCycle() {
|
||||||
|
MockParentSupplier.DEFAULT_LIFECYCLE = LifeCycle.GLOBAL;
|
||||||
|
ParentTest test = new ParentTest();
|
||||||
|
|
||||||
|
registry.beforeEach(test);
|
||||||
|
MockParentValue value1 = test.parent;
|
||||||
|
assertRunning(value1);
|
||||||
|
|
||||||
|
registry.afterEach();
|
||||||
|
registry.afterAll();
|
||||||
|
assertRunning(value1);
|
||||||
|
|
||||||
|
ParentTest test2 = new ParentTest();
|
||||||
|
registry.beforeEach(test2);
|
||||||
|
MockParentValue value2 = test2.parent;
|
||||||
|
|
||||||
|
Assertions.assertSame(value1, value2);
|
||||||
|
|
||||||
|
registry.close();
|
||||||
|
|
||||||
|
assertClosed(value1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testClassLifeCycle() {
|
||||||
|
ParentTest test = new ParentTest();
|
||||||
|
|
||||||
|
registry.beforeEach(test);
|
||||||
|
MockParentValue value1 = test.parent;
|
||||||
|
assertRunning(value1);
|
||||||
|
|
||||||
|
registry.afterEach();
|
||||||
|
assertRunning(value1);
|
||||||
|
|
||||||
|
registry.beforeEach(test);
|
||||||
|
MockParentValue value2 = test.parent;
|
||||||
|
Assertions.assertSame(value1, value2);
|
||||||
|
|
||||||
|
registry.afterEach();
|
||||||
|
assertRunning(value1);
|
||||||
|
|
||||||
|
registry.afterAll();
|
||||||
|
assertClosed(value1);
|
||||||
|
|
||||||
|
ParentTest test2 = new ParentTest();
|
||||||
|
registry.beforeEach(test2);
|
||||||
|
MockParentValue value3 = test2.parent;
|
||||||
|
|
||||||
|
Assertions.assertNotSame(value1, value3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMethodLifeCycle() {
|
||||||
|
MockParentSupplier.DEFAULT_LIFECYCLE = LifeCycle.METHOD;
|
||||||
|
|
||||||
|
ParentTest test = new ParentTest();
|
||||||
|
|
||||||
|
registry.beforeEach(test);
|
||||||
|
MockParentValue value1 = test.parent;
|
||||||
|
assertRunning(value1);
|
||||||
|
|
||||||
|
registry.afterEach();
|
||||||
|
assertClosed(value1);
|
||||||
|
|
||||||
|
registry.beforeEach(test);
|
||||||
|
MockParentValue value2 = test.parent;
|
||||||
|
Assertions.assertNotSame(value1, value2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLifeCycleBetweenDependencies() {
|
||||||
|
MockParentSupplier.DEFAULT_LIFECYCLE = LifeCycle.METHOD;
|
||||||
|
MockChildSupplier.DEFAULT_LIFECYCLE = LifeCycle.GLOBAL;
|
||||||
|
|
||||||
|
ParentAndChildTest parentAndChildTest = new ParentAndChildTest();
|
||||||
|
MockChildValue child1 = parentAndChildTest.child;
|
||||||
|
|
||||||
|
registry.beforeEach(parentAndChildTest);
|
||||||
|
assertRunning(parentAndChildTest.parent, parentAndChildTest.child);
|
||||||
|
Assertions.assertSame(parentAndChildTest.parent, parentAndChildTest.child.getParent());
|
||||||
|
|
||||||
|
registry.afterEach();
|
||||||
|
assertClosed(parentAndChildTest.parent, parentAndChildTest.child);
|
||||||
|
|
||||||
|
registry.beforeEach(parentAndChildTest);
|
||||||
|
Assertions.assertNotSame(child1, parentAndChildTest.child);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDependencyCreatedOnDemand() {
|
||||||
|
ChildTest childTest = new ChildTest();
|
||||||
|
|
||||||
|
registry.beforeEach(childTest);
|
||||||
|
assertRunning(childTest.child, childTest.child.getParent());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void assertRunning(Object... values) {
|
||||||
|
MatcherAssert.assertThat(MockInstances.INSTANCES, Matchers.hasItems(values));
|
||||||
|
MatcherAssert.assertThat(MockInstances.INSTANCES, Matchers.hasSize(values.length));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void assertClosed(Object... values) {
|
||||||
|
MatcherAssert.assertThat(MockInstances.CLOSED_INSTANCES, Matchers.hasItems(values));
|
||||||
|
MatcherAssert.assertThat(MockInstances.CLOSED_INSTANCES, Matchers.hasSize(values.length));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class ParentAndChildTest {
|
||||||
|
@MockParentAnnotation
|
||||||
|
MockParentValue parent;
|
||||||
|
|
||||||
|
@MockChildAnnotation
|
||||||
|
MockChildValue child;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class ParentTest {
|
||||||
|
@MockParentAnnotation
|
||||||
|
MockParentValue parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class ChildTest {
|
||||||
|
@MockChildAnnotation
|
||||||
|
MockChildValue child;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package org.keycloak.test.framework.injection.mocks;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.FIELD)
|
||||||
|
public @interface MockChildAnnotation {
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package org.keycloak.test.framework.injection.mocks;
|
||||||
|
|
||||||
|
import org.keycloak.test.framework.injection.InstanceContext;
|
||||||
|
import org.keycloak.test.framework.injection.LifeCycle;
|
||||||
|
import org.keycloak.test.framework.injection.RequestedInstance;
|
||||||
|
import org.keycloak.test.framework.injection.Supplier;
|
||||||
|
|
||||||
|
public class MockChildSupplier implements Supplier<MockChildValue, MockChildAnnotation> {
|
||||||
|
|
||||||
|
public static LifeCycle DEFAULT_LIFECYCLE = LifeCycle.CLASS;
|
||||||
|
|
||||||
|
public static void reset() {
|
||||||
|
DEFAULT_LIFECYCLE = LifeCycle.CLASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<MockChildAnnotation> getAnnotationClass() {
|
||||||
|
return MockChildAnnotation.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<MockChildValue> getValueType() {
|
||||||
|
return MockChildValue.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MockChildValue getValue(InstanceContext<MockChildValue, MockChildAnnotation> instanceContext) {
|
||||||
|
MockParentValue mockParentValue = instanceContext.getDependency(MockParentValue.class);
|
||||||
|
return new MockChildValue(mockParentValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean compatible(InstanceContext<MockChildValue, MockChildAnnotation> a, RequestedInstance<MockChildValue, MockChildAnnotation> b) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close(InstanceContext<MockChildValue, MockChildAnnotation> instanceContext) {
|
||||||
|
instanceContext.getValue().close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LifeCycle getDefaultLifecycle() {
|
||||||
|
return DEFAULT_LIFECYCLE;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package org.keycloak.test.framework.injection.mocks;
|
||||||
|
|
||||||
|
public class MockChildValue {
|
||||||
|
|
||||||
|
private final MockParentValue parent;
|
||||||
|
|
||||||
|
public MockChildValue(MockParentValue parent) {
|
||||||
|
MockInstances.INSTANCES.add(this);
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MockParentValue getParent() {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
if (parent.isClosed()) {
|
||||||
|
throw new RuntimeException("Parent is closed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean removed = MockInstances.INSTANCES.remove(this);
|
||||||
|
if (!removed) {
|
||||||
|
throw new RuntimeException("Instance already removed");
|
||||||
|
} else {
|
||||||
|
MockInstances.CLOSED_INSTANCES.add(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package org.keycloak.test.framework.injection.mocks;
|
||||||
|
|
||||||
|
import io.vertx.core.impl.ConcurrentHashSet;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class MockInstances {
|
||||||
|
|
||||||
|
public static final Set<Object> INSTANCES = new ConcurrentHashSet<>();
|
||||||
|
public static final Set<Object> CLOSED_INSTANCES = new ConcurrentHashSet<>();
|
||||||
|
|
||||||
|
public static void reset() {
|
||||||
|
INSTANCES.clear();
|
||||||
|
CLOSED_INSTANCES.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package org.keycloak.test.framework.injection.mocks;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.FIELD)
|
||||||
|
public @interface MockParentAnnotation {
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package org.keycloak.test.framework.injection.mocks;
|
||||||
|
|
||||||
|
import org.keycloak.test.framework.injection.InstanceContext;
|
||||||
|
import org.keycloak.test.framework.injection.LifeCycle;
|
||||||
|
import org.keycloak.test.framework.injection.RequestedInstance;
|
||||||
|
import org.keycloak.test.framework.injection.Supplier;
|
||||||
|
|
||||||
|
public class MockParentSupplier implements Supplier<MockParentValue, MockParentAnnotation> {
|
||||||
|
|
||||||
|
public static LifeCycle DEFAULT_LIFECYCLE = LifeCycle.CLASS;
|
||||||
|
|
||||||
|
public static void reset() {
|
||||||
|
DEFAULT_LIFECYCLE = LifeCycle.CLASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<MockParentAnnotation> getAnnotationClass() {
|
||||||
|
return MockParentAnnotation.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<MockParentValue> getValueType() {
|
||||||
|
return MockParentValue.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MockParentValue getValue(InstanceContext<MockParentValue, MockParentAnnotation> instanceContext) {
|
||||||
|
return new MockParentValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean compatible(InstanceContext<MockParentValue, MockParentAnnotation> a, RequestedInstance<MockParentValue, MockParentAnnotation> b) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close(InstanceContext<MockParentValue, MockParentAnnotation> instanceContext) {
|
||||||
|
instanceContext.getValue().close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LifeCycle getDefaultLifecycle() {
|
||||||
|
return DEFAULT_LIFECYCLE;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package org.keycloak.test.framework.injection.mocks;
|
||||||
|
|
||||||
|
public class MockParentValue {
|
||||||
|
|
||||||
|
private boolean closed = false;
|
||||||
|
|
||||||
|
public MockParentValue() {
|
||||||
|
MockInstances.INSTANCES.add(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isClosed() {
|
||||||
|
return closed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
boolean removed = MockInstances.INSTANCES.remove(this);
|
||||||
|
if (!removed) {
|
||||||
|
throw new RuntimeException("Instance already removed");
|
||||||
|
} else {
|
||||||
|
MockInstances.CLOSED_INSTANCES.add(this);
|
||||||
|
}
|
||||||
|
closed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
org.keycloak.test.framework.injection.mocks.MockParentSupplier
|
||||||
|
org.keycloak.test.framework.injection.mocks.MockChildSupplier
|
15
test-poc/framework/src/test/resources/logging.properties
Normal file
15
test-poc/framework/src/test/resources/logging.properties
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
loggers=org.keycloak.test
|
||||||
|
logger.org.keycloak.test.level=TRACE
|
||||||
|
|
||||||
|
logger.handlers=CONSOLE
|
||||||
|
|
||||||
|
handler.CONSOLE=org.jboss.logmanager.handlers.ConsoleHandler
|
||||||
|
handler.CONSOLE.properties=autoFlush
|
||||||
|
handler.CONSOLE.level=ERROR
|
||||||
|
handler.CONSOLE.autoFlush=true
|
||||||
|
handler.CONSOLE.formatter=PATTERN
|
||||||
|
|
||||||
|
# The log format pattern for both logs
|
||||||
|
formatter.PATTERN=org.jboss.logmanager.formatters.PatternFormatter
|
||||||
|
formatter.PATTERN.properties=pattern
|
||||||
|
formatter.PATTERN.pattern=%d{HH:mm:ss,SSS} %-5p %t [%c] %m%n
|
Loading…
Reference in a new issue