001 /** 002 * Copyright 2005-2013 The Kuali Foundation 003 * 004 * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php 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 package org.kuali.rice.test; 017 018 import org.junit.Assert; 019 020 import java.lang.annotation.Annotation; 021 import java.lang.reflect.Method; 022 import java.util.ArrayList; 023 import java.util.List; 024 import java.util.Set; 025 026 public final class TestUtilities { 027 028 private static Thread exceptionThreader; 029 030 private TestUtilities() { 031 throw new UnsupportedOperationException("do not call"); 032 } 033 034 /** 035 * Waits "indefinately" for the exception routing thread to terminate. 036 * 037 * This actually doesn't wait forever but puts an upper bound of 5 minutes 038 * on the time to wait for the exception routing thread to complete. If a 039 * document cannot go into exception routing within 5 minutes then we got 040 * problems. 041 */ 042 public static void waitForExceptionRouting() { 043 waitForExceptionRouting(5*60*1000); 044 } 045 046 public static void waitForExceptionRouting(long milliseconds) { 047 try { 048 Thread thread = getExceptionThreader(); 049 if (thread == null) { 050 throw new IllegalStateException("No exception thread was established, likely message is not being processed for exception routing."); 051 } 052 thread.join(milliseconds); 053 } catch (InterruptedException e) { 054 Assert.fail("This thread was interuppted while waiting for exception routing."); 055 } 056 if (getExceptionThreader().isAlive()) { 057 Assert.fail("Document was not put into exception routing within the specified amount of time " + milliseconds); 058 } 059 } 060 061 public static Thread getExceptionThreader() { 062 return exceptionThreader; 063 } 064 065 public static void setExceptionThreader(Thread exceptionThreader) { 066 TestUtilities.exceptionThreader = exceptionThreader; 067 } 068 069 protected static boolean contains(Class[] list, Class target) { 070 for (Class c: list) { 071 if (c.getName().equals(target.getName())) { 072 return true; 073 } 074 } 075 return false; 076 } 077 078 /** 079 * This method facilitates using annotations in a unit test class hierarchy. We walk up the class hierarchy 080 * and on each class, looking for the presence of any of the specified annotation types. If the particular class 081 * defines one of the annotation types, it is marked for handling. Once any single target annotation is found 082 * on the class, it is marked and no further annotations are inspected. 083 * 084 * If the annotation defines an 'overrideSuperClasses' method, and this method returns false, then processing 085 * continues up the class hierarchy. Otherwise processing stops when the first annotation is found. Note that 086 * this feature only makes sense when specifying a single annotation type. 087 * 088 * After a list of classes in descending hierarchy order is compiled, the list is iterated over (again, in 089 * descending hierarchy order) and if the class is not already present in the caller-supplied list of classes 090 * already handled by the caller, the class is added to a list of classes that need to be handled by the caller, 091 * which is then returned to the caller. 092 * 093 * It is the caller's responsibility to handle the returned classes, and store them in some internal list which it may 094 * give back to this method in the future. 095 * 096 * @throws Exception if there is a problem in reflection on an Annotation object 097 */ 098 public static List<Class> getHierarchyClassesToHandle(Class testClass, Class[] annotationClasses, Set<String> classesHandled) throws Exception { 099 List<Class> classesThatNeedHandling = new ArrayList<Class>(); 100 // get a list of all classes the current class extends from that use the PerSuiteUnitTestData annotation 101 List<Class> classesToCheck = new ArrayList<Class>(); 102 // here we get the list apart checking the annotations to support the perSuiteDataLoaderLifecycleNamesRun variable better 103 { 104 Class clazz = testClass; 105 superClassLoop: while (!clazz.getName().equals(Object.class.getName())) { 106 for (Annotation annotation : clazz.getDeclaredAnnotations()) { 107 // if this isn't one of the annotations we're interested in, move on 108 if (!contains(annotationClasses, annotation.annotationType())) { 109 continue; 110 } 111 112 // this class should be processed because it contains an annotation we are interested in 113 classesToCheck.add(0, clazz); 114 115 // now check to see if annotation overrides super class implementations 116 if (annotationOverridesSuperClass(annotation)) { 117 // we're done here 118 break superClassLoop; 119 } 120 // we just added the class to classes to check, we don't need to add it again 121 // so just stop looking at annotations in this particular class 122 break; 123 } 124 clazz = clazz.getSuperclass(); 125 } 126 } 127 128 for (Class clazz: classesToCheck) { 129 if (!classesHandled.contains(clazz.getName())) { 130 classesThatNeedHandling.add(clazz); 131 } 132 } 133 return classesThatNeedHandling; 134 } 135 136 /** 137 * Determines whether an annotation should override the same type of annotation on a superclass, 138 * by using reflection to invoke the 'overrideSuperClasses' method on the annotation if it exists. 139 * If the annotation does not supply this method, the default is true. 140 * @param annotation the annotation to inspect 141 * @return whether this annotation overrides any annotations of similar type in super classes 142 * @throws Exception if an error occurs during reflection 143 */ 144 protected static boolean annotationOverridesSuperClass(Annotation annotation) throws Exception { 145 boolean overrides = true; // default is to just override 146 Method m = null;; 147 try { 148 m = annotation.getClass().getMethod("overrideSuperClasses", null); 149 } catch (NoSuchMethodException nsme) { 150 // do nothing 151 } 152 if (m != null) { 153 Object result = m.invoke(annotation, (Object[]) null); 154 if (result instanceof Boolean) { 155 overrides = (Boolean) result; 156 } else { 157 throw new RuntimeException("Annotation 'overrideSuperClasses' did not return Boolean value"); 158 } 159 } 160 return overrides; 161 } 162 }