001/* 002 * (C) Copyright 2014-2015 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Stephane Lacoin, Julien Carsique 018 * 019 */ 020package org.nuxeo.runtime.test.runner; 021 022import java.lang.annotation.ElementType; 023import java.lang.annotation.Retention; 024import java.lang.annotation.RetentionPolicy; 025import java.lang.annotation.Target; 026import java.lang.reflect.Field; 027import java.lang.reflect.Method; 028 029import javax.inject.Inject; 030import javax.inject.Named; 031 032import org.apache.commons.lang3.SystemUtils; 033import org.junit.Rule; 034import org.junit.rules.MethodRule; 035import org.junit.rules.TestRule; 036import org.junit.runner.Description; 037import org.junit.runner.notification.RunNotifier; 038import org.junit.runners.model.FrameworkMethod; 039import org.junit.runners.model.Statement; 040 041public class ConditionalIgnoreRule implements TestRule, MethodRule { 042 @Inject 043 private RunNotifier runNotifier; 044 045 @Inject 046 private FeaturesRunner runner; 047 048 public static class Feature extends SimpleFeature { 049 protected static final ConditionalIgnoreRule rule = new ConditionalIgnoreRule(); 050 051 @Rule 052 public MethodRule methodRule() { 053 return rule; 054 } 055 056 @Rule 057 public static TestRule testRule() { 058 return rule; 059 } 060 061 } 062 063 @Retention(RetentionPolicy.RUNTIME) 064 @Target({ ElementType.TYPE, ElementType.METHOD }) 065 public @interface Ignore { 066 Class<? extends Condition> condition(); 067 068 /** 069 * Optional reason why the test is ignored, reported additionally to the condition class simple name. 070 */ 071 String cause() default ""; 072 } 073 074 public interface Condition { 075 boolean shouldIgnore(); 076 } 077 078 public static final class NXP10926H2Upgrade implements Condition { 079 @Override 080 public boolean shouldIgnore() { 081 return false; 082 } 083 } 084 085 public static final class IgnoreIsolated implements Condition { 086 boolean isIsolated = "org.nuxeo.runtime.testsuite.IsolatedClassloader".equals( 087 getClass().getClassLoader().getClass().getName()); 088 089 @Override 090 public boolean shouldIgnore() { 091 return isIsolated; 092 } 093 } 094 095 public static final class IgnoreLongRunning implements Condition { 096 @Override 097 public boolean shouldIgnore() { 098 return true; 099 } 100 } 101 102 public static final class IgnoreWindows implements Condition { 103 @Override 104 public boolean shouldIgnore() { 105 return SystemUtils.IS_OS_WINDOWS; 106 } 107 } 108 109 @Override 110 public Statement apply(Statement base, Description description) { 111 Ignore ignore = runner.getConfig(Ignore.class); 112 Class<? extends Condition> conditionType = ignore.condition(); 113 if (conditionType == null) { 114 return base; 115 } 116 return new Statement() { 117 118 @Override 119 public void evaluate() throws Throwable { 120 // as this is a TestRule / Rule (see Feature#testRule) runtime was already started 121 // because we are here after BeforeClassStatement (runtime start) and before 122 // the MethodRule / Rule execution which cause processing of method annotations, but here we just want 123 // to check condition on the class which doesn't depend on method annotations 124 if (newCondition(null, null, null, conditionType).shouldIgnore()) { 125 runNotifier.fireTestIgnored(description); 126 } else { 127 base.evaluate(); 128 } 129 } 130 }; 131 } 132 133 @Override 134 public Statement apply(Statement base, FrameworkMethod frameworkMethod, Object target) { 135 Ignore ignore = runner.getConfig(frameworkMethod, Ignore.class); 136 Class<? extends Condition> conditionType = ignore.condition(); 137 if (conditionType == null) { 138 return base; 139 } 140 Class<?> type = target.getClass(); 141 Method method = frameworkMethod.getMethod(); 142 Description description = Description.createTestDescription(type, method.getName(), method.getAnnotations()); 143 return new Statement() { 144 145 @Override 146 public void evaluate() throws Throwable { 147 if (newCondition(type, method, target, conditionType).shouldIgnore()) { 148 runNotifier.fireTestIgnored(description); 149 } else { 150 base.evaluate(); 151 } 152 } 153 }; 154 } 155 156 protected Condition newCondition(Class<?> type, Method method, Object target, 157 Class<? extends Condition> conditionType) throws Error { 158 Condition condition; 159 try { 160 condition = conditionType.newInstance(); 161 } catch (InstantiationException | IllegalAccessException cause) { 162 throw new Error("Cannot instantiate condition of type " + conditionType, cause); 163 } 164 injectCondition(type, method, target, condition); 165 return condition; 166 } 167 168 protected void injectCondition(Class<?> type, Method method, Object target, Condition condition) 169 throws SecurityException, Error { 170 Error errors = new Error("Cannot inject condition parameters in " + condition.getClass()); 171 for (Field eachField : condition.getClass().getDeclaredFields()) { 172 if (!eachField.isAnnotationPresent(Inject.class)) { 173 continue; 174 } 175 Object eachValue = null; 176 if (eachField.isAnnotationPresent(Named.class)) { 177 String name = eachField.getAnnotation(Named.class).value(); 178 if ("type".equals(name)) { 179 eachValue = type; 180 } else if ("target".equals(name)) { 181 eachValue = target; 182 } else if ("method".equals(name)) { 183 eachValue = method; 184 } 185 } else { 186 Class<?> eachType = eachField.getType(); 187 if (eachType.equals(Class.class)) { 188 eachValue = type; 189 } else if (eachType.equals(Object.class)) { 190 eachValue = target; 191 } else if (eachType.equals(Method.class)) { 192 eachValue = method; 193 } 194 } 195 if (eachValue == null) { 196 continue; 197 } 198 eachField.setAccessible(true); 199 try { 200 eachField.set(condition, eachValue); 201 } catch (IllegalArgumentException | IllegalAccessException cause) { 202 errors.addSuppressed(new Error("Cannot inject " + eachField.getName(), cause)); 203 } 204 } 205 if (errors.getSuppressed().length > 0) { 206 throw errors; 207 } 208 runner.getInjector().injectMembers(condition); 209 } 210 211}