[KEYCLOAK-17031] - ClientInvalidationClusterTest failing on Quarkus due to unreliable comparison
This commit is contained in:
parent
f4b5942c6c
commit
eb37a1ed69
3 changed files with 125 additions and 1 deletions
|
@ -35,6 +35,7 @@ import java.security.AccessController;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
@ -409,6 +410,20 @@ public class Reflections {
|
||||||
return member;
|
return member;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the accessibility flag on the {@link AccessibleObject} to false as described in {@link
|
||||||
|
* AccessibleObject#setAccessible(boolean)} within the context of a {link PrivilegedAction}.
|
||||||
|
*
|
||||||
|
* @param <A> member the accessible object type
|
||||||
|
* @param member the accessible object
|
||||||
|
*
|
||||||
|
* @return the accessible object after the accessible flag has been altered
|
||||||
|
*/
|
||||||
|
public static <A extends AccessibleObject> A unsetAccessible(A member) {
|
||||||
|
AccessController.doPrivileged(new UnSetAccessiblePrivilegedAction(member));
|
||||||
|
return member;
|
||||||
|
}
|
||||||
|
|
||||||
private static String buildSetFieldValueErrorMessage(Field field, Object obj, Object value) {
|
private static String buildSetFieldValueErrorMessage(Field field, Object obj, Object value) {
|
||||||
return String.format("Exception setting [%s] field on object [%s] to value [%s]", field.getName(), obj, value);
|
return String.format("Exception setting [%s] field on object [%s] to value [%s]", field.getName(), obj, value);
|
||||||
}
|
}
|
||||||
|
@ -994,4 +1009,43 @@ public class Reflections {
|
||||||
public static <T> T newInstance(final Class<?> type, final String fullQualifiedName) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
|
public static <T> T newInstance(final Class<?> type, final String fullQualifiedName) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
|
||||||
return (T) classForName(fullQualifiedName, type.getClassLoader()).newInstance();
|
return (T) classForName(fullQualifiedName, type.getClassLoader()).newInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Resolves the type of items for a {@link Field} declared as a {@link List}.
|
||||||
|
*
|
||||||
|
* <p>This method will first try to check the parametrized type of the field type. If none is defined, it will try to infer
|
||||||
|
* the type of items by looking at the value of the field for the given {@code instance}.
|
||||||
|
*
|
||||||
|
* <p>Make sure the field is accessible before invoking this method.
|
||||||
|
*
|
||||||
|
* @param field the field declared as {@link List}
|
||||||
|
* @param instance the instance that should be used to obtain infer the type in case no parametrized type is found in the field.
|
||||||
|
* @return if the field is not a {@link List}, it returns null. Otherwise the type of items of the list. If the type for items can not be inferred, the {@link Object} type is returned.
|
||||||
|
* @throws IllegalAccessException in case it fails to obtain the value of the field from the {@code instance}
|
||||||
|
*/
|
||||||
|
public static Class<?> resolveListType(Field field, Object instance) throws IllegalAccessException {
|
||||||
|
if (!List.class.isAssignableFrom(field.getType())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Type genericType = field.getGenericType();
|
||||||
|
|
||||||
|
if (genericType instanceof ParameterizedType) {
|
||||||
|
Type[] typeArguments = ParameterizedType.class.cast(genericType)
|
||||||
|
.getActualTypeArguments();
|
||||||
|
|
||||||
|
if (typeArguments[0] instanceof Class) {
|
||||||
|
return (Class<?>) typeArguments[0];
|
||||||
|
}
|
||||||
|
} else if (instance != null) {
|
||||||
|
// just in case the field is not parametrized
|
||||||
|
List item = List.class.cast(field.get(instance));
|
||||||
|
|
||||||
|
if (!item.isEmpty()) {
|
||||||
|
return item.get(0).getClass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.class;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.common.util.reflections;
|
||||||
|
|
||||||
|
import java.lang.reflect.AccessibleObject;
|
||||||
|
import java.security.PrivilegedAction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link PrivilegedAction} that calls {@link AccessibleObject#setAccessible(boolean)}
|
||||||
|
*/
|
||||||
|
public class UnSetAccessiblePrivilegedAction implements PrivilegedAction<Void> {
|
||||||
|
|
||||||
|
private final AccessibleObject member;
|
||||||
|
|
||||||
|
public UnSetAccessiblePrivilegedAction(AccessibleObject member) {
|
||||||
|
this.member = member;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Void run() {
|
||||||
|
if (member.isAccessible()) {
|
||||||
|
member.setAccessible(false);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -9,10 +9,15 @@ import org.junit.Test;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.testsuite.arquillian.ContainerInfo;
|
import org.keycloak.testsuite.arquillian.ContainerInfo;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.keycloak.common.util.reflections.Reflections.resolveListType;
|
||||||
|
import static org.keycloak.common.util.reflections.Reflections.setAccessible;
|
||||||
|
import static org.keycloak.common.util.reflections.Reflections.unsetAccessible;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -132,7 +137,8 @@ public abstract class AbstractInvalidationClusterTest<T, TR> extends AbstractClu
|
||||||
boolean entityDiffers = false;
|
boolean entityDiffers = false;
|
||||||
for (ContainerInfo survivorNode : getCurrentSurvivorNodes()) {
|
for (ContainerInfo survivorNode : getCurrentSurvivorNodes()) {
|
||||||
T testEntityOnSurvivorNode = readEntity(testEntityOnFailNode, survivorNode);
|
T testEntityOnSurvivorNode = readEntity(testEntityOnFailNode, survivorNode);
|
||||||
if (EqualsBuilder.reflectionEquals(testEntityOnSurvivorNode, testEntityOnFailNode, excludedComparisonFields)) {
|
|
||||||
|
if (EqualsBuilder.reflectionEquals(sortFields(testEntityOnSurvivorNode), sortFields(testEntityOnFailNode), excludedComparisonFields)) {
|
||||||
log.info(String.format("Verification of %s on survivor %s PASSED", getEntityType(testEntityOnFailNode), survivorNode));
|
log.info(String.format("Verification of %s on survivor %s PASSED", getEntityType(testEntityOnFailNode), survivorNode));
|
||||||
} else {
|
} else {
|
||||||
entityDiffers = true;
|
entityDiffers = true;
|
||||||
|
@ -149,6 +155,29 @@ public abstract class AbstractInvalidationClusterTest<T, TR> extends AbstractClu
|
||||||
assertFalse(entityDiffers);
|
assertFalse(entityDiffers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private T sortFields(T entity) {
|
||||||
|
for (Field field : entity.getClass().getDeclaredFields()) {
|
||||||
|
try {
|
||||||
|
Class<?> type = resolveListType(field, entity);
|
||||||
|
|
||||||
|
if (type != null && Comparable.class.isAssignableFrom(type)) {
|
||||||
|
setAccessible(field);
|
||||||
|
Object value = field.get(entity);
|
||||||
|
|
||||||
|
if (value != null) {
|
||||||
|
Collections.sort((List) value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IllegalAccessException cause) {
|
||||||
|
throw new RuntimeException("Failed to sort field [" + field + "]", cause);
|
||||||
|
} finally {
|
||||||
|
unsetAccessible(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
private void assertEntityOnSurvivorNodesIsDeleted(T testEntityOnFailNode) {
|
private void assertEntityOnSurvivorNodesIsDeleted(T testEntityOnFailNode) {
|
||||||
// check if deleted from all survivor nodes
|
// check if deleted from all survivor nodes
|
||||||
boolean entityExists = false;
|
boolean entityExists = false;
|
||||||
|
|
Loading…
Reference in a new issue