View Javadoc
1   package auxtestlib;
2   
3   import java.lang.reflect.Field;
4   import java.util.ArrayList;
5   import java.util.Arrays;
6   import java.util.Comparator;
7   import java.util.List;
8   import java.util.Properties;
9   
10  import org.apache.commons.lang.ArrayUtils;
11  import org.junit.After;
12  import org.junit.Assert;
13  import org.junit.Before;
14  
15  /**
16   * Super class for all test cases. Test helpers (see
17   * {@link AbstractTestHelper}) may be added as private variables with the
18   * {@link TestHelper} annotation. They will be automatically initialized and
19   * destroyed. Note that helpers will be initialized by alphabetical order and
20   * will be destroyed in reverse order. This class will save system properties
21   * on set up and will restore them at tear down.
22   */
23  public class DefaultTCase extends Assert {
24  	/**
25  	 * Interval between checking for an expression becoming <code>true</code>.
26  	 */
27  	private static final long WAIT_FOR_TRUE_SLEEP_MS = 10;
28  	
29  	/**
30  	 * Default timeout to wait for an expression to become <code>true</code>.
31  	 */
32  	private static final long WAIT_FOR_TRUE_DEFAULT_TIMEOUT_MS = 10_000;
33  	
34  	/**
35  	 * All helpers.
36  	 */
37  	private List<Field> m_helpers;
38  	
39  	/**
40  	 * Saved system properties.
41  	 */
42  	private Properties m_system_properties;
43  	
44  	/**
45  	 * Runs before any preparation. Ensures there are no helpers pending from
46  	 * previous test cases. It will also initialize any helpers marked with the
47  	 * {@link TestHelper} annotation.
48  	 * @throws Exception failed to initialize helpers.
49  	 */
50  	@Before
51  	public void pre_set_up() throws Exception {
52  		/*
53  		 * We're in trouble if this is not true...
54  		 */
55  		assert m_helpers == null;
56  		
57  		/*
58  		 * Ensure global properties are loaded.
59  		 */
60  		TestPropertiesDefinition.load_global_properties();
61  		
62  		/*
63  		 * Save all system properties with the modifications from the test
64  		 * properties but before test code has been run.
65  		 */
66  		m_system_properties = (Properties) System.getProperties().clone();
67  
68  		/*
69  		 * Check that there are no helpers pending from previous runs.
70  		 */
71  		assertEquals(0, AbstractTestHelper.getTotalHelperCount());
72  
73  		/*
74  		 * Initialize helpers.
75  		 */
76  		m_helpers = new ArrayList<>();
77  
78  		for (Class<?> cls = getClass(); cls != null;
79  				cls = cls.getSuperclass()) {
80  			Field[] fields = cls.getDeclaredFields();
81  			Arrays.sort(fields, new Comparator<Field>() {
82  				@Override
83  				public int compare(Field o1, Field o2) {
84  					return o1.getName().compareTo(o2.getName());
85  				}
86  			});
87  			for (Field f : fields) {
88  				if (f.getAnnotation(TestHelper.class) != null) {
89  					if (!f.isAccessible()) {
90  						f.setAccessible(true);
91  					}
92  
93  					Class<?> ftype = f.getType();
94  					if (!AbstractTestHelper.class.isAssignableFrom(ftype)) {
95  						throw new TestCaseConfigurationException("Field '"
96  								+ f.toString() + "' " + "of type '"
97  								+ cls.getCanonicalName() + "' has type '"
98  								+ ftype.getCanonicalName()
99  								+ "' which is not a subclass of "
100 								+ "AbstractTestHelper.");
101 					}
102 
103 					m_helpers.add(f);
104 					f.set(this, f.getType().newInstance());
105 				}
106 			}
107 		}
108 	}
109 
110 	/**
111 	 * Runs after tear down. Ensures all helpers have been destroyed.
112 	 * @throws Exception failed to tear down the test case
113 	 */
114 	@After
115 	public void post_tear_down() throws Exception {
116 		/*
117 		 * Not sure what means if this doesn't hold.
118 		 */
119 		assert m_helpers != null;
120 
121 		/*
122 		 * Dispose of all helpers.
123 		 */
124 		Field[] fr = new Field[m_helpers.size()];
125 		ArrayUtils.reverse(m_helpers.toArray(fr));
126 		for (Field f : fr) {
127 			AbstractTestHelper ath = (AbstractTestHelper) f.get(this);
128 			ath.tearDown();
129 		}
130 
131 		m_helpers = null;
132 
133 		/*
134 		 * At the end there should be no more helpers.
135 		 */
136 		assertEquals(0, AbstractTestHelper.getTotalHelperCount());
137 
138 		/*
139 		 * Sets all system properties back to their original values.
140 		 */
141 		System.setProperties(m_system_properties);
142 	}
143 	
144 	/**
145 	 * Artificially invoke all automatically generated methods of an
146 	 * enumeration to make sure they are not missing in the code coverage
147 	 * report.
148 	 * @param cls the enumeration class
149 	 * @param <E> the enumeration type
150 	 * @throws Exception failed to invoke
151 	 */
152 	protected <E extends Enum<E>> void cover_enumeration(Class<E> cls)
153 			throws Exception {
154 		E[] values = cls.getEnumConstants();
155 		for (E e : values) {
156 			e.toString();
157 			Enum.valueOf(cls, e.name());
158 			cls.getMethod("valueOf", String.class).invoke(null, e.name());
159 		}
160 		
161 		cls.getMethod("values").invoke(null);
162 	}
163 	
164 	/**
165 	 * Keeps evaluating an expression until it returns <code>true</code> or
166 	 * until it times out. This method is usually used on unit tests to avoid
167 	 * having to code thread sleeps. This method will invoke
168 	 * <code>fail()</code> if <code>eval</code> didn't become
169 	 * <code>true</code> after <code>timeout_ms</code> milliseconds have
170 	 * elapsed
171 	 * @param eval the expression to evaluate; this expression will be invoked
172 	 * multiple times and it should compute quickly
173 	 * @param timeout_ms the timeout in milliseconds
174 	 * @throws Exception failed to evaluate
175 	 */
176 	protected void wait_for_true(BooleanEvaluation eval, long timeout_ms)
177 			throws Exception {
178 		if (eval == null) {
179 			throw new IllegalArgumentException("eval == null");
180 		}
181 		
182 		if (timeout_ms <= 0) {
183 			throw new IllegalArgumentException("timeout_ms <= 0");
184 		}
185 		
186 		long end = System.currentTimeMillis() + timeout_ms;
187 		do {
188 			if (eval.evaluate()) {
189 				return;
190 			}
191 			
192 			Thread.sleep(WAIT_FOR_TRUE_SLEEP_MS);
193 		} while (System.currentTimeMillis() < end);
194 		
195 		fail();
196 	}
197 	
198 	/**
199 	 * Equivalent to invoke {@link #wait_for_true(BooleanEvaluation, long)}
200 	 * with {@link #WAIT_FOR_TRUE_DEFAULT_TIMEOUT_MS} as timeout.
201 	 * @param eval the expression to evaluate; this expression will be invoked
202 	 * multiple times and it should compute quickly
203 	 * @throws Exception failed to evaluate
204 	 */
205 	protected void wait_for_true(BooleanEvaluation eval) throws Exception {
206 		wait_for_true(eval, WAIT_FOR_TRUE_DEFAULT_TIMEOUT_MS);
207 	}
208 	
209 	/**
210 	 * Obtains the value of a field in an object. This method is usually used
211 	 * to mess up with internals for white box unit testing.
212 	 * @param <FIELD_T> the type of field
213 	 * @param object the object, cannot be <code>null</code>
214 	 * @param field_type the type of field, cannot be <code>null</code>
215 	 * @param field_name the name of the field, cannot be <code>null</code>
216 	 * @return the value of the field
217 	 * @throws Exception failed to access the field
218 	 */
219 	public static <FIELD_T> FIELD_T internal_get(Object object,
220 			Class<FIELD_T> field_type, String field_name) throws Exception {
221 		if (object == null) {
222 			throw new IllegalArgumentException("object == null");
223 		}
224 		
225 		if (field_type == null) {
226 			throw new IllegalArgumentException("field_type == null");
227 		}
228 		
229 		if (field_name == null) {
230 			throw new IllegalArgumentException("field_name == null");
231 		}
232 		
233 		Class<?> class_type = object.getClass();
234 		
235 		Field f = class_type.getDeclaredField(field_name);
236 		if (f == null) {
237 			throw new Exception("No field '" + field_name + "' found in class '"
238 					+ class_type.getName() + "'.");
239 		}
240 		
241 		f.setAccessible(true);
242 		if (!field_type.isAssignableFrom(f.getType())) {
243 			throw new Exception("Field '" + field_name + "' in class '"
244 					+ class_type.getName() + "' is not assignable to type '"
245 					+ field_type.getName() + "'.");
246 		}
247 		
248 		return field_type.cast(f.get(object));
249 	}
250 	
251 	/**
252 	 * Obtains the value of a field in an object. This method is usually used
253 	 * to mess up with internals for white box unit testing.
254 	 * @param object the object, cannot be <code>null</code>
255 	 * @param field_name the name of the field, cannot be <code>null</code>
256 	 * @param value the value to put in the field
257 	 * @throws Exception failed to access the field
258 	 */
259 	public static void internal_set(Object object, String field_name,
260 			Object value) throws Exception {
261 		if (object == null) {
262 			throw new IllegalArgumentException("object == null");
263 		}
264 		
265 		if (field_name == null) {
266 			throw new IllegalArgumentException("field_name == null");
267 		}
268 		
269 		Class<?> class_type = object.getClass();
270 		
271 		Field f = class_type.getDeclaredField(field_name);
272 		if (f == null) {
273 			throw new Exception("No field '" + field_name + "' found in class '"
274 					+ class_type.getName() + "'.");
275 		}
276 		
277 		f.setAccessible(true);
278 		if (value != null) {
279 			if (!f.getType().isAssignableFrom(value.getClass())) {
280 				throw new Exception("Field '" + field_name + "' in class '"
281 						+ class_type.getName() + "' cannot receive value of "
282 						+ "type '"+ value.getClass().getName() + "'.");
283 			}
284 			
285 		}
286 		
287 		f.set(object, value);
288 	}
289 }