Add initial tests for the framework registry (#31433)

Closes #31432

Signed-off-by: stianst <stianst@gmail.com>
This commit is contained in:
Stian Thorgersen 2024-07-19 14:45:14 +02:00 committed by GitHub
parent 6afc8c3e04
commit 02e3339762
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 367 additions and 6 deletions

View file

@ -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;
}
} }

View file

@ -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);
requestedInstances.add(requestedServerInstance); if (requestedServerInstance != null) {
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);
} }

View file

@ -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;
}
}

View file

@ -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 {
}

View file

@ -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;
}
}

View file

@ -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);
}
}
}

View file

@ -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();
}
}

View file

@ -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 {
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -0,0 +1,2 @@
org.keycloak.test.framework.injection.mocks.MockParentSupplier
org.keycloak.test.framework.injection.mocks.MockChildSupplier

View 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