[KEYCLOAK-17031] - ClientInvalidationClusterTest failing on Quarkus due to unreliable comparison

This commit is contained in:
Pedro Igor 2021-02-04 19:52:02 -03:00 committed by Marek Posolda
parent f4b5942c6c
commit eb37a1ed69
3 changed files with 125 additions and 1 deletions

View file

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

View file

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

View file

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