1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * See LICENSE.txt included in this distribution for the specific 9 * language governing permissions and limitations under the License. 10 * 11 * When distributing Covered Code, include this CDDL HEADER in each 12 * file and include the License file at LICENSE.txt. 13 * If applicable, add the following below this CDDL HEADER, with the 14 * fields enclosed by brackets "[]" replaced with your own identifying 15 * information: Portions Copyright [yyyy] [name of copyright owner] 16 * 17 * CDDL HEADER END 18 */ 19 20 /* 21 * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. 22 */ 23 package org.opengrok.indexer.condition; 24 25 import java.lang.reflect.Method; 26 import java.lang.reflect.Modifier; 27 import java.util.ArrayList; 28 import java.util.LinkedList; 29 import java.util.List; 30 import org.junit.Assume; 31 import org.junit.rules.TestRule; 32 import org.junit.runner.Description; 33 import org.junit.runners.model.Statement; 34 35 /** 36 * This rule can be added to a Junit test and will look for the annotation 37 * {@link ConditionalRun} on either the test class or method. The test is then 38 * skipped through Junit's {@link Assume} capabilities if the 39 * {@link RunCondition} provided in the annotation is not satisfied. 40 * 41 * Cobbled together from: 42 * http://www.codeaffine.com/2013/11/18/a-junit-rule-to-conditionally-ignore-tests/ 43 * https://gist.github.com/yinzara/9980184 44 * http://cwd.dhemery.com/2010/12/junit-rules/ 45 * http://stackoverflow.com/questions/28145735/androidjunit4-class-org-junit-assume-assumetrue-assumptionviolatedexception/ 46 * https://docs.oracle.com/javase/tutorial/java/annotations/repeating.html 47 */ 48 public class ConditionalRunRule implements TestRule { 49 50 @Override apply(Statement aStatement, Description aDescription)51 public Statement apply(Statement aStatement, Description aDescription) { 52 if (hasConditionalIgnoreAnnotationOnMethod(aDescription)) { 53 RunCondition condition = getIgnoreConditionOnMethod(aDescription); 54 if (!condition.isForcedOrSatisfied()) { 55 return new IgnoreStatement(condition); 56 } 57 } 58 59 if (hasConditionalIgnoreAnnotationOnClass(aDescription)) { 60 RunCondition condition = getIgnoreConditionOnClass(aDescription); 61 if (!condition.isForcedOrSatisfied()) { 62 return new IgnoreStatement(condition); 63 } 64 } 65 66 return aStatement; 67 } 68 hasConditionalIgnoreAnnotationOnClass(Description aDescription)69 private static boolean hasConditionalIgnoreAnnotationOnClass(Description aDescription) { 70 return aDescription.getTestClass().getAnnotationsByType(ConditionalRun.class).length > 0; 71 } 72 getIgnoreConditionOnClass(Description aDescription)73 private static RunCondition getIgnoreConditionOnClass(Description aDescription) { 74 ConditionalRun[] annotations = aDescription.getTestClass().getAnnotationsByType(ConditionalRun.class); 75 return new IgnoreConditionCreator(aDescription.getTestClass(), annotations).create(); 76 } 77 hasConditionalIgnoreAnnotationOnMethod(Description aDescription)78 private static boolean hasConditionalIgnoreAnnotationOnMethod(Description aDescription) { 79 if (aDescription.getMethodName() == null) { // if @ClassRule is used 80 return false; 81 } 82 try { 83 // this is possible because test methods must not have any argument 84 Method testMethod = aDescription.getTestClass().getMethod(aDescription.getMethodName()); 85 return testMethod.getAnnotationsByType(ConditionalRun.class).length > 0; 86 } catch (NoSuchMethodException | SecurityException ex) { 87 throw new RuntimeException(ex); 88 } 89 } 90 getIgnoreConditionOnMethod(Description aDescription)91 private static RunCondition getIgnoreConditionOnMethod(Description aDescription) { 92 try { 93 // this is possible because test methods must not have any argument 94 ConditionalRun[] annotations = aDescription.getTestClass().getMethod(aDescription.getMethodName()).getAnnotationsByType(ConditionalRun.class); 95 return new IgnoreConditionCreator(aDescription.getTestClass(), annotations).create(); 96 } catch (NoSuchMethodException | SecurityException ex) { 97 throw new RuntimeException(ex); 98 } 99 } 100 101 /** 102 * Container for several conditions joined by an AND operator. 103 */ 104 protected static class CompositeCondition implements RunCondition { 105 106 List<RunCondition> conditions = new LinkedList<>(); 107 add(RunCondition e)108 public boolean add(RunCondition e) { 109 return conditions.add(e); 110 } 111 112 @Override isSatisfied()113 public boolean isSatisfied() { 114 for (RunCondition condition : conditions) { 115 if (!condition.isSatisfied()) { 116 return false; 117 } 118 } 119 return true; 120 } 121 } 122 123 protected static class IgnoreConditionCreator { 124 125 private final Class<?> mTestClass; 126 private final List<Class<? extends RunCondition>> conditionTypes; 127 IgnoreConditionCreator(Class<?> aTestClass, ConditionalRun[] annotation)128 public IgnoreConditionCreator(Class<?> aTestClass, ConditionalRun[] annotation) { 129 this.mTestClass = aTestClass; 130 this.conditionTypes = new ArrayList<>(annotation.length); 131 for (int i = 0; i < annotation.length; i++) { 132 this.conditionTypes.add(i, annotation[i].value()); 133 } 134 } 135 create()136 public RunCondition create() { 137 checkConditionType(); 138 try { 139 return createCondition(); 140 } catch (RuntimeException re) { 141 throw re; 142 } catch (Exception e) { 143 throw new RuntimeException(e); 144 } 145 } 146 createCondition()147 private RunCondition createCondition() throws Exception { 148 CompositeCondition result = null; 149 /** 150 * Run through the list of classes implementing RunCondition and 151 * create a new class from it. 152 */ 153 for (Class<? extends RunCondition> clazz : conditionTypes) { 154 if (result == null) { 155 result = new CompositeCondition(); 156 } 157 if (isConditionTypeStandalone(clazz)) { 158 result.add(clazz.getDeclaredConstructor().newInstance()); 159 } else { 160 result.add(clazz.getDeclaredConstructor(mTestClass).newInstance(mTestClass)); 161 } 162 } 163 return result; 164 } 165 checkConditionType()166 private void checkConditionType() { 167 for (Class<? extends RunCondition> clazz : conditionTypes) { 168 if (!isConditionTypeStandalone(clazz) && !isConditionTypeDeclaredInTarget(clazz)) { 169 String msg 170 = "Conditional class '%s' is a member class " 171 + "but was not declared inside the test case using it.\n" 172 + "Either make this class a static class, " 173 + "standalone class (by declaring it in it's own file) " 174 + "or move it inside the test case using it"; 175 throw new IllegalArgumentException(String.format(msg, clazz.getName())); 176 } 177 } 178 } 179 isConditionTypeStandalone(Class<? extends RunCondition> clazz)180 private boolean isConditionTypeStandalone(Class<? extends RunCondition> clazz) { 181 return !clazz.isMemberClass() 182 || Modifier.isStatic(clazz.getModifiers()); 183 } 184 isConditionTypeDeclaredInTarget(Class<? extends RunCondition> clazz)185 private boolean isConditionTypeDeclaredInTarget(Class<? extends RunCondition> clazz) { 186 return mTestClass.getClass().isAssignableFrom(clazz.getDeclaringClass()); 187 } 188 } 189 190 protected static class IgnoreStatement extends Statement { 191 192 private final RunCondition condition; 193 IgnoreStatement(RunCondition condition)194 IgnoreStatement(RunCondition condition) { 195 this.condition = condition; 196 } 197 198 @Override evaluate()199 public void evaluate() { 200 Assume.assumeTrue("Ignored by " + condition.getClass().getSimpleName(), false); 201 } 202 } 203 } 204