package org.infinispan.test.fwk; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.function.Predicate; import org.testng.IMethodInstance; import org.testng.IMethodInterceptor; import org.testng.ITestContext; /** * This is a workaround for TestNG limitation allowing only single IMethodInterceptor instance. * Allows to use multiple {@link TestSelector} annotations in the test class hieararchy. * * Filters are executed before interceptors, and only on those classes that define them. Filters * should not have any side-effect and as these only remove test methods, the order of execution * is not important. * * The interceptors on superclasses will be executed before interceptors on subclasses, but * an interceptor is executed even on a class that does not define it (because the interceptor * is invoked once for the whole suite). * * @author Radim Vansa <rvansa@redhat.com> */ public class ChainMethodInterceptor implements IMethodInterceptor { @Override public List<IMethodInstance> intercept(List<IMethodInstance> methods, ITestContext context) { Set<Class<? extends IMethodInterceptor>> interceptorSet = new HashSet<>(); List<Class<? extends IMethodInterceptor>> interceptorList = new ArrayList<>(); Set<Class<? extends Predicate<IMethodInstance>>> filters = new HashSet<>(); for (IMethodInstance method : methods) { findInterceptors(method.getInstance().getClass(), interceptorSet, interceptorList, filters); } if (!filters.isEmpty()) { Predicate<IMethodInstance>[] filterInstances = filters.stream().map(clazz -> { try { return clazz.newInstance(); } catch (Exception e) { throw new IllegalStateException("Cannot construct filter", e); } }).toArray(Predicate[]::new); ArrayList<IMethodInstance> filteredMethods = new ArrayList<>(methods.size()); METHODS: for (IMethodInstance m : methods) { for (Predicate<IMethodInstance> filter : filterInstances) { if (hasFilter(m.getInstance().getClass(), filter.getClass()) && !filter.test(m)) continue METHODS; } filteredMethods.add(m); } methods = filteredMethods; } for (Class<? extends IMethodInterceptor> interceptor : interceptorList) { try { methods = interceptor.newInstance().intercept(methods, context); } catch (Exception e) { throw new RuntimeException(e); } } return methods; } private boolean hasFilter(Class<?> clazz, Class<? extends Predicate> filter) { if (clazz == null || clazz == Object.class) return false; TestSelector annotation = clazz.getAnnotation(TestSelector.class); if (annotation != null) { for (Class<? extends Predicate<IMethodInstance>> f : annotation.filters()) { if (f == filter) return true; } } return hasFilter(clazz.getSuperclass(), filter); } private void findInterceptors(Class<?> clazz, Set<Class<? extends IMethodInterceptor>> interceptorSet, List<Class<? extends IMethodInterceptor>> interceptorList, Set<Class<? extends Predicate<IMethodInstance>>> filters) { if (clazz == null || clazz.equals(Object.class)) return; findInterceptors(clazz.getSuperclass(), interceptorSet, interceptorList, filters); TestSelector annotation = clazz.getAnnotation(TestSelector.class); if (annotation != null) { for (Class<? extends IMethodInterceptor> interceptor : annotation.interceptors()) { if (interceptorSet.add(interceptor)) { interceptorList.add(interceptor); } } for (Class<? extends Predicate<IMethodInstance>> filter : annotation.filters()) { filters.add(filter); } } } }