Skip to content

Commit aa6890f

Browse files
authored
Support running test methods on the server side (#44937)
Closes #44936 Signed-off-by: stianst <[email protected]>
1 parent 9597537 commit aa6890f

File tree

12 files changed

+271
-71
lines changed

12 files changed

+271
-71
lines changed
Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,91 @@
11
package org.keycloak.testframework;
22

3+
import java.lang.reflect.Method;
34
import java.util.Optional;
45

5-
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
66
import org.keycloak.testframework.injection.Registry;
77

88
import org.junit.jupiter.api.extension.AfterAllCallback;
99
import org.junit.jupiter.api.extension.AfterEachCallback;
1010
import org.junit.jupiter.api.extension.BeforeAllCallback;
1111
import org.junit.jupiter.api.extension.BeforeEachCallback;
1212
import org.junit.jupiter.api.extension.ExtensionContext;
13+
import org.junit.jupiter.api.extension.InvocationInterceptor;
14+
import org.junit.jupiter.api.extension.ParameterContext;
15+
import org.junit.jupiter.api.extension.ParameterResolutionException;
16+
import org.junit.jupiter.api.extension.ParameterResolver;
17+
import org.junit.jupiter.api.extension.ReflectiveInvocationContext;
1318
import org.junit.jupiter.api.extension.TestWatcher;
1419

15-
public class KeycloakIntegrationTestExtension implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback, AfterAllCallback, TestWatcher {
20+
public class KeycloakIntegrationTestExtension implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback, AfterAllCallback, TestWatcher, InvocationInterceptor, ParameterResolver {
1621

1722
private static final LogHandler logHandler = new LogHandler();
1823

1924
@Override
2025
public void beforeAll(ExtensionContext context) {
21-
if (isExtensionEnabled(context)) {
22-
logHandler.beforeAll(context);
23-
}
26+
logHandler.beforeAll(context);
2427
}
2528

2629
@Override
2730
public void beforeEach(ExtensionContext context) {
28-
if (isExtensionEnabled(context)) {
29-
logHandler.beforeEachStarting(context);
30-
getRegistry(context).beforeEach(context.getRequiredTestInstance());
31-
logHandler.beforeEachCompleted(context);
32-
}
31+
logHandler.beforeEachStarting(context);
32+
getRegistry(context).beforeEach(context.getRequiredTestInstance(), context.getRequiredTestMethod());
33+
logHandler.beforeEachCompleted(context);
3334
}
3435

3536
@Override
3637
public void afterEach(ExtensionContext context) {
37-
if (isExtensionEnabled(context)) {
38-
logHandler.afterEachStarting(context);
39-
getRegistry(context).afterEach();
40-
logHandler.afterEachCompleted(context);
41-
}
38+
logHandler.afterEachStarting(context);
39+
getRegistry(context).afterEach();
40+
logHandler.afterEachCompleted(context);
4241
}
4342

4443
@Override
4544
public void afterAll(ExtensionContext context) {
46-
if (isExtensionEnabled(context)) {
47-
logHandler.afterAll(context);
48-
getRegistry(context).afterAll();
49-
}
45+
logHandler.afterAll(context);
46+
getRegistry(context).afterAll();
5047
}
5148

5249
@Override
5350
public void testFailed(ExtensionContext context, Throwable cause) {
54-
if (isExtensionEnabled(context)) {
55-
logHandler.testFailed(context);
56-
}
51+
logHandler.testFailed(context);
5752
}
5853

5954
@Override
6055
public void testDisabled(ExtensionContext context, Optional<String> reason) {
61-
if (isExtensionEnabled(context)) {
62-
logHandler.testDisabled(context);
63-
}
56+
logHandler.testDisabled(context);
6457
}
6558

6659
@Override
6760
public void testSuccessful(ExtensionContext context) {
68-
if (isExtensionEnabled(context)) {
69-
logHandler.testSuccessful(context);
70-
}
61+
logHandler.testSuccessful(context);
7162
}
7263

7364
@Override
7465
public void testAborted(ExtensionContext context, Throwable cause) {
75-
if (isExtensionEnabled(context)) {
76-
logHandler.testAborted(context);
77-
}
66+
logHandler.testAborted(context);
7867
}
7968

80-
private boolean isExtensionEnabled(ExtensionContext context) {
81-
return context.getRequiredTestClass().isAnnotationPresent(KeycloakIntegrationTest.class);
69+
@Override
70+
public void interceptTestMethod(Invocation<Void> invocation, ReflectiveInvocationContext<Method> invocationContext, ExtensionContext extensionContext) throws Throwable {
71+
getRegistry(extensionContext).intercept(invocation, invocationContext);
8272
}
8373

84-
private Registry getRegistry(ExtensionContext context) {
74+
public static Registry getRegistry(ExtensionContext context) {
8575
ExtensionContext.Store store = context.getRoot().getStore(ExtensionContext.Namespace.GLOBAL);
8676
Registry registry = (Registry) store.getOrComputeIfAbsent(Registry.class, r -> new Registry());
8777
registry.setCurrentContext(context);
8878
return registry;
8979
}
9080

81+
@Override
82+
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext context) throws ParameterResolutionException {
83+
return getRegistry(context).supportsParameter(parameterContext, context);
84+
}
85+
86+
@Override
87+
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext context) throws ParameterResolutionException {
88+
// As this is only used by custom test executors for now they are responsible for injecting the parameter, hence returning null here
89+
return null;
90+
}
9191
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.keycloak.testframework;
2+
3+
import java.lang.reflect.Method;
4+
import java.util.List;
5+
6+
import org.keycloak.testframework.injection.Registry;
7+
8+
public interface TestFrameworkExecutor {
9+
10+
List<Class<?>> getMethodValueTypes(Method method);
11+
12+
boolean supportsParameter(Method method, Class<?> parameterType);
13+
14+
boolean shouldExecute(Method testMethod);
15+
16+
void execute(Registry registry, Class<?> testClass, Method testMethod);
17+
18+
}

test-framework/core/src/main/java/org/keycloak/testframework/annotations/KeycloakIntegrationTest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@
55
import java.lang.annotation.RetentionPolicy;
66
import java.lang.annotation.Target;
77

8+
import org.keycloak.testframework.KeycloakIntegrationTestExtension;
89
import org.keycloak.testframework.server.DefaultKeycloakServerConfig;
910
import org.keycloak.testframework.server.KeycloakServerConfig;
1011

12+
import org.junit.jupiter.api.extension.ExtendWith;
13+
1114
@Retention(RetentionPolicy.RUNTIME)
1215
@Target(ElementType.TYPE)
16+
@ExtendWith({KeycloakIntegrationTestExtension.class})
1317
public @interface KeycloakIntegrationTest {
1418

1519
Class<? extends KeycloakServerConfig> config() default DefaultKeycloakServerConfig.class;

test-framework/core/src/main/java/org/keycloak/testframework/injection/Extensions.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22

33
import java.lang.annotation.Annotation;
44
import java.lang.reflect.Field;
5+
import java.lang.reflect.Method;
56
import java.util.Arrays;
67
import java.util.HashSet;
78
import java.util.LinkedList;
89
import java.util.List;
910
import java.util.ServiceLoader;
1011
import java.util.Set;
1112

13+
import org.keycloak.testframework.TestFrameworkExecutor;
1214
import org.keycloak.testframework.TestFrameworkExtension;
1315
import org.keycloak.testframework.config.Config;
1416

@@ -22,6 +24,7 @@ public class Extensions {
2224
private final List<Class<?>> alwaysEnabledValueTypes;
2325

2426
private static Extensions INSTANCE;
27+
private final List<TestFrameworkExtension> extensions;
2528

2629
public static Extensions getInstance() {
2730
if (INSTANCE == null) {
@@ -35,7 +38,7 @@ public static void reset() {
3538
}
3639

3740
private Extensions() {
38-
List<TestFrameworkExtension> extensions = loadExtensions();
41+
extensions = loadExtensions();
3942
valueTypeAlias = loadValueTypeAlias(extensions);
4043
Config.registerValueTypeAlias(valueTypeAlias);
4144
logger = new RegistryLogger(valueTypeAlias);
@@ -55,6 +58,19 @@ public List<Class<?>> getAlwaysEnabledValueTypes() {
5558
return alwaysEnabledValueTypes;
5659
}
5760

61+
public List<TestFrameworkExecutor> getTestFrameworkExecutors() {
62+
return extensions.stream()
63+
.filter(e -> e instanceof TestFrameworkExecutor)
64+
.map(e -> (TestFrameworkExecutor) e)
65+
.toList();
66+
}
67+
68+
public List<Class<?>> getMethodValueTypes(Method method) {
69+
return getTestFrameworkExecutors()
70+
.stream()
71+
.flatMap(e -> e.getMethodValueTypes(method).stream()).toList();
72+
}
73+
5874
@SuppressWarnings("unchecked")
5975
public <T> Supplier<T, ?> findSupplierByType(Class<T> typeClass) {
6076
return (Supplier<T, ?>) suppliers.stream().filter(s -> s.getValueType().equals(typeClass)).findFirst().orElse(null);

test-framework/core/src/main/java/org/keycloak/testframework/injection/Registry.java

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,20 @@
22

33
import java.lang.annotation.Annotation;
44
import java.lang.reflect.Field;
5+
import java.lang.reflect.Method;
56
import java.util.Comparator;
67
import java.util.Iterator;
78
import java.util.LinkedList;
89
import java.util.List;
910
import java.util.Objects;
1011
import java.util.Set;
1112

13+
import org.keycloak.testframework.TestFrameworkExecutor;
14+
1215
import org.junit.jupiter.api.extension.ExtensionContext;
16+
import org.junit.jupiter.api.extension.InvocationInterceptor;
17+
import org.junit.jupiter.api.extension.ParameterContext;
18+
import org.junit.jupiter.api.extension.ReflectiveInvocationContext;
1319

1420
@SuppressWarnings({"rawtypes", "unchecked"})
1521
public class Registry implements ExtensionContext.Store.CloseableResource {
@@ -112,16 +118,36 @@ private <T> T getUnConfiguredDependency(Class<T> typeClass, String ref, Instance
112118
return (T) dependency.getValue();
113119
}
114120

115-
public void beforeEach(Object testInstance) {
116-
findRequestedInstances(testInstance);
121+
public void beforeEach(Object testInstance, Method testMethod) {
122+
findRequestedInstances(testInstance, testMethod);
117123
destroyIncompatibleInstances();
118124
matchDeployedInstancesWithRequestedInstances();
119125
deployRequestedInstances();
120126
injectFields(testInstance);
121127
invokeBeforeEachOnSuppliers();
122128
}
123129

124-
private void findRequestedInstances(Object testInstance) {
130+
public void intercept(InvocationInterceptor.Invocation<Void> invocation, ReflectiveInvocationContext<Method> invocationContext) throws Throwable {
131+
Class<?> testClass = invocationContext.getTargetClass();
132+
Method testMethod = invocationContext.getExecutable();
133+
134+
TestFrameworkExecutor testFrameworkExecutor = getExecutor(testMethod);
135+
if (testFrameworkExecutor != null) {
136+
testFrameworkExecutor.execute(this, testClass, testMethod);
137+
invocation.skip();
138+
} else {
139+
invocation.proceed();
140+
}
141+
}
142+
143+
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
144+
Method testMethod = (Method) parameterContext.getParameter().getDeclaringExecutable();
145+
Class<?> parameterType = parameterContext.getParameter().getType();
146+
TestFrameworkExecutor testFrameworkExecutor = getExecutor(testMethod);
147+
return testFrameworkExecutor != null && testFrameworkExecutor.supportsParameter(testMethod, parameterType);
148+
}
149+
150+
private void findRequestedInstances(Object testInstance, Method testMethod) {
125151
List<Class<?>> alwaysEnabledValueTypes = extensions.getAlwaysEnabledValueTypes();
126152
for (Class<?> valueType : alwaysEnabledValueTypes) {
127153
RequestedInstance requestedInstance = createRequestedInstance(null, valueType);
@@ -130,6 +156,14 @@ private void findRequestedInstances(Object testInstance) {
130156
}
131157
}
132158

159+
List<Class<?>> methodValueTypes = extensions.getMethodValueTypes(testMethod);
160+
for (Class<?> valueType : methodValueTypes) {
161+
RequestedInstance requestedInstance = createRequestedInstance(null, valueType);
162+
if (requestedInstance != null) {
163+
requestedInstances.add(requestedInstance);
164+
}
165+
}
166+
133167
Class testClass = testInstance.getClass();
134168
RequestedInstance requestedServerInstance = createRequestedInstance(testClass.getAnnotations(), null);
135169
if (requestedServerInstance != null) {
@@ -317,6 +351,10 @@ private void invokeBeforeEachOnSuppliers() {
317351
}
318352
}
319353

354+
private TestFrameworkExecutor getExecutor(Method testMethod) {
355+
return extensions.getTestFrameworkExecutors().stream().filter(e -> e.shouldExecute(testMethod)).findFirst().orElse(null);
356+
}
357+
320358
private static class RequestedInstanceComparator implements Comparator<RequestedInstance> {
321359

322360
static final RequestedInstanceComparator INSTANCE = new RequestedInstanceComparator();

test-framework/core/src/main/resources/META-INF/services/org.junit.jupiter.api.extension.Extension

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)