001 // Copyright 2004, 2005 The Apache Software Foundation
002 //
003 // Licensed under the Apache License, Version 2.0 (the "License");
004 // you may not use this file except in compliance with the License.
005 // You may obtain a copy of the License at
006 //
007 // http://www.apache.org/licenses/LICENSE-2.0
008 //
009 // Unless required by applicable law or agreed to in writing, software
010 // distributed under the License is distributed on an "AS IS" BASIS,
011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012 // See the License for the specific language governing permissions and
013 // limitations under the License.
014
015 package org.apache.hivemind.test;
016
017 import java.net.URL;
018 import java.util.ArrayList;
019 import java.util.Iterator;
020 import java.util.List;
021 import java.util.Locale;
022
023 import junit.framework.AssertionFailedError;
024 import junit.framework.TestCase;
025
026 import org.apache.hivemind.ApplicationRuntimeException;
027 import org.apache.hivemind.ClassResolver;
028 import org.apache.hivemind.Location;
029 import org.apache.hivemind.Registry;
030 import org.apache.hivemind.Resource;
031 import org.apache.hivemind.definition.ModuleDefinition;
032 import org.apache.hivemind.definition.RegistryDefinition;
033 import org.apache.hivemind.definition.impl.RegistryDefinitionImpl;
034 import org.apache.hivemind.impl.DefaultClassResolver;
035 import org.apache.hivemind.impl.LocationImpl;
036 import org.apache.hivemind.impl.RegistryBuilder;
037 import org.apache.hivemind.internal.ser.ServiceSerializationHelper;
038 import org.apache.hivemind.util.ClasspathResource;
039 import org.apache.hivemind.util.PropertyUtils;
040 import org.apache.hivemind.util.URLResource;
041 import org.apache.log4j.Level;
042 import org.apache.log4j.LogManager;
043 import org.apache.log4j.Logger;
044 import org.apache.log4j.spi.LoggingEvent;
045 import org.apache.oro.text.regex.Pattern;
046 import org.apache.oro.text.regex.Perl5Compiler;
047 import org.apache.oro.text.regex.Perl5Matcher;
048 import org.easymock.MockControl;
049 import org.easymock.classextension.MockClassControl;
050
051 /**
052 * Contains some support for creating HiveMind tests; this is useful enough that has been moved into
053 * the main framework, to simplify creation of tests in the dependent libraries.
054 *
055 * @author Howard Lewis Ship
056 */
057 public abstract class HiveMindTestCase extends TestCase
058 {
059 // /CLOVER:OFF
060
061 /**
062 * An instance of {@link DefaultClassResolver} that can be used by tests.
063 */
064
065 private ClassResolver _classResolver;
066
067 protected String _interceptedLoggerName;
068
069 protected StoreAppender _appender;
070
071 private static Perl5Compiler _compiler;
072
073 private static Perl5Matcher _matcher;
074
075 /** List of {@link org.easymock.MockControl}. */
076
077 private List _controls = new ArrayList();
078
079 /** @since 1.1 */
080 interface MockControlFactory
081 {
082 public MockControl newControl(Class mockClass);
083 }
084
085 /** @since 1.1 */
086 private static class InterfaceMockControlFactory implements MockControlFactory
087 {
088 public MockControl newControl(Class mockClass)
089 {
090 return MockControl.createStrictControl(mockClass);
091 }
092 }
093
094 /** @since 1.1 */
095 private static class ClassMockControlFactory implements MockControlFactory
096 {
097 public MockControl newControl(Class mockClass)
098 {
099 return MockClassControl.createStrictControl(mockClass);
100 }
101 }
102
103 /** @since 1.1 */
104 static class PlaceholderClassMockControlFactory implements MockControlFactory
105 {
106 public MockControl newControl(Class mockClass)
107 {
108 throw new RuntimeException(
109 "Unable to instantiate EasyMock control for "
110 + mockClass
111 + "; ensure that easymockclassextension-1.1.jar and cglib-full-2.0.1.jar are on the classpath.");
112 }
113 }
114
115 /** @since 1.1 */
116 private static final MockControlFactory _interfaceMockControlFactory = new InterfaceMockControlFactory();
117
118 /** @since 1.1 */
119 private static MockControlFactory _classMockControlFactory;
120
121 static
122 {
123 try
124 {
125 _classMockControlFactory = new ClassMockControlFactory();
126 }
127 catch (NoClassDefFoundError ex)
128 {
129 _classMockControlFactory = new PlaceholderClassMockControlFactory();
130 }
131 }
132
133 /**
134 * Returns the given file as a {@link Resource} from the classpath. Typically, this is to find
135 * files in the same folder as the invoking class.
136 */
137 protected Resource getResource(String file)
138 {
139 URL url = getClass().getResource(file);
140
141 if (url == null)
142 throw new NullPointerException("No resource named '" + file + "'.");
143
144 return new URLResource(url);
145 }
146
147 /**
148 * Converts the actual list to an array and invokes
149 * {@link #assertListsEqual(Object[], Object[])}.
150 */
151 protected static void assertListsEqual(Object[] expected, List actual)
152 {
153 assertListsEqual(expected, actual.toArray());
154 }
155
156 /**
157 * Asserts that the two arrays are equal; same length and all elements equal. Checks the
158 * elements first, then the length.
159 */
160 protected static void assertListsEqual(Object[] expected, Object[] actual)
161 {
162 assertNotNull(actual);
163
164 int min = Math.min(expected.length, actual.length);
165
166 for (int i = 0; i < min; i++)
167 assertEquals("list[" + i + "]", expected[i], actual[i]);
168
169 assertEquals("list length", expected.length, actual.length);
170 }
171
172 /**
173 * Called when code should not be reachable (because a test is expected to throw an exception);
174 * throws AssertionFailedError always.
175 */
176 protected static void unreachable()
177 {
178 throw new AssertionFailedError("This code should be unreachable.");
179 }
180
181 /**
182 * Sets up an appender to intercept logging for the specified logger. Captured log events can be
183 * recovered via {@link #getInterceptedLogEvents()}.
184 */
185 protected void interceptLogging(String loggerName)
186 {
187 Logger logger = LogManager.getLogger(loggerName);
188
189 logger.removeAllAppenders();
190
191 _interceptedLoggerName = loggerName;
192 _appender = new StoreAppender();
193 _appender.activateOptions();
194
195 logger.setLevel(Level.DEBUG);
196 logger.setAdditivity(false);
197 logger.addAppender(_appender);
198 }
199
200 /**
201 * Gets the list of events most recently intercepted. This resets the appender, clearing the
202 * list of stored events.
203 *
204 * @see #interceptLogging(String)
205 */
206
207 protected List getInterceptedLogEvents()
208 {
209 return _appender.getEvents();
210 }
211
212 /**
213 * Removes the appender that may have been setup by {@link #interceptLogging(String)}. Also,
214 * invokes {@link org.apache.hivemind.util.PropertyUtils#clearCache()}.
215 */
216 protected void tearDown() throws Exception
217 {
218 super.tearDown();
219
220 if (_appender != null)
221 {
222 _appender = null;
223
224 Logger logger = LogManager.getLogger(_interceptedLoggerName);
225 logger.setLevel(null);
226 logger.setAdditivity(true);
227 logger.removeAllAppenders();
228 }
229
230 PropertyUtils.clearCache();
231
232 ServiceSerializationHelper.setServiceSerializationSupport(null);
233 }
234
235 /**
236 * Checks that the provided substring exists in the exceptions message.
237 */
238 protected void assertExceptionSubstring(Throwable ex, String substring)
239 {
240 String message = ex.getMessage();
241 assertNotNull(message);
242
243 int pos = message.indexOf(substring);
244
245 if (pos < 0)
246 throw new AssertionFailedError("Exception message (" + message + ") does not contain ["
247 + substring + "]");
248 }
249
250 /**
251 * Checks that the message for an exception matches a regular expression.
252 */
253
254 protected void assertExceptionRegexp(Throwable ex, String pattern) throws Exception
255 {
256 String message = ex.getMessage();
257 assertNotNull(message);
258
259 setupMatcher();
260
261 Pattern compiled = _compiler.compile(pattern);
262
263 if (_matcher.contains(message, compiled))
264 return;
265
266 throw new AssertionFailedError("Exception message (" + message
267 + ") does not contain regular expression [" + pattern + "].");
268 }
269
270 protected void assertRegexp(String pattern, String actual) throws Exception
271 {
272 setupMatcher();
273
274 Pattern compiled = _compiler.compile(pattern);
275
276 if (_matcher.contains(actual, compiled))
277 return;
278
279 throw new AssertionFailedError("\"" + actual + "\" does not contain regular expression["
280 + pattern + "].");
281 }
282
283 /**
284 * Digs down through (potentially) a stack of ApplicationRuntimeExceptions until it reaches the
285 * originating exception, which is returned.
286 */
287 protected Throwable findNestedException(ApplicationRuntimeException ex)
288 {
289 Throwable cause = ex.getRootCause();
290
291 if (cause == null || cause == ex)
292 return ex;
293
294 if (cause instanceof ApplicationRuntimeException)
295 return findNestedException((ApplicationRuntimeException) cause);
296
297 return cause;
298 }
299
300 /**
301 * Checks to see if a specific event matches the name and message.
302 *
303 * @param message
304 * exact message to search for
305 * @param events
306 * the list of events {@link #getInterceptedLogEvents()}
307 * @param index
308 * the index to check at
309 */
310 private void assertLoggedMessage(String message, List events, int index)
311 {
312 LoggingEvent e = (LoggingEvent) events.get(index);
313
314 assertEquals("Message", message, e.getMessage());
315 }
316
317 /**
318 * Checks the messages for all logged events for exact match against the supplied list.
319 */
320 protected void assertLoggedMessages(String[] messages)
321 {
322 List events = getInterceptedLogEvents();
323
324 for (int i = 0; i < messages.length; i++)
325 {
326 assertLoggedMessage(messages[i], events, i);
327 }
328 }
329
330 /**
331 * Asserts that some capture log event matches the given message exactly.
332 */
333 protected void assertLoggedMessage(String message)
334 {
335 assertLoggedMessage(message, getInterceptedLogEvents());
336 }
337
338 /**
339 * Asserts that some capture log event matches the given message exactly.
340 *
341 * @param message
342 * to search for; success is finding a logged message contain the parameter as a
343 * substring
344 * @param events
345 * from {@link #getInterceptedLogEvents()}
346 */
347 protected void assertLoggedMessage(String message, List events)
348 {
349 int count = events.size();
350
351 for (int i = 0; i < count; i++)
352 {
353 LoggingEvent e = (LoggingEvent) events.get(i);
354
355 String eventMessage = String.valueOf(e.getMessage());
356
357 if (eventMessage.indexOf(message) >= 0)
358 return;
359 }
360
361 throw new AssertionFailedError("Could not find logged message: " + message);
362 }
363
364 protected void assertLoggedMessagePattern(String pattern) throws Exception
365 {
366 assertLoggedMessagePattern(pattern, getInterceptedLogEvents());
367 }
368
369 protected void assertLoggedMessagePattern(String pattern, List events) throws Exception
370 {
371 setupMatcher();
372
373 Pattern compiled = null;
374
375 int count = events.size();
376
377 for (int i = 0; i < count; i++)
378 {
379 LoggingEvent e = (LoggingEvent) events.get(i);
380
381 String eventMessage = e.getMessage().toString();
382
383 if (compiled == null)
384 compiled = _compiler.compile(pattern);
385
386 if (_matcher.contains(eventMessage, compiled))
387 return;
388
389 }
390
391 throw new AssertionFailedError("Could not find logged message with pattern: " + pattern);
392 }
393
394 private void setupMatcher()
395 {
396 if (_compiler == null)
397 _compiler = new Perl5Compiler();
398
399 if (_matcher == null)
400 _matcher = new Perl5Matcher();
401 }
402
403 protected Registry buildFrameworkRegistry(ModuleDefinition customModule)
404 {
405 return buildFrameworkRegistry(new ModuleDefinition[] {customModule});
406 }
407
408 /**
409 * Builds a registry, containing only the modules delivered the parameter
410 * <code>customModules</code>, plus the master module descriptor
411 * (i.e., those visible on the classpath).
412 */
413 protected Registry buildFrameworkRegistry(ModuleDefinition[] customModules)
414 {
415 RegistryDefinition registryDefinition = new RegistryDefinitionImpl();
416 for (int i = 0; i < customModules.length; i++)
417 {
418 ModuleDefinition module = customModules[i];
419 registryDefinition.addModule(module);
420 }
421
422 RegistryBuilder builder = new RegistryBuilder(registryDefinition);
423 return builder.constructRegistry(Locale.getDefault());
424 }
425
426 /**
427 * Builds a registry from exactly the provided resource; this registry will not include the
428 * <code>hivemind</code> module.
429 */
430 protected Registry buildMinimalRegistry(Resource l) throws Exception
431 {
432 RegistryBuilder builder = new RegistryBuilder();
433
434 return builder.constructRegistry(Locale.getDefault());
435 }
436
437 /**
438 * Creates a <em>managed</em> control via
439 * {@link MockControl#createStrictControl(java.lang.Class)}. The created control is remembered,
440 * and will be invoked by {@link #replayControls()}, {@link #verifyControls()}, etc.
441 * <p>
442 * The class to mock may be either an interface or a class. The EasyMock class extension
443 * (easymockclassextension-1.1.jar) and CGLIB (cglib-full-2.01.jar) must be present in the
444 * latter case (new since 1.1).
445 * <p>
446 * This method is not deprecated, but is rarely used; typically {@link #newMock(Class)} is used
447 * to create the control and the mock, and {@link #setReturnValue(Object, Object)} and
448 * {@link #setThrowable(Object, Throwable)} are used to while training it.
449 * {@link #getControl(Object)} is used for the rare cases where the MockControl itself is
450 * needed.
451 */
452 protected MockControl newControl(Class mockClass)
453 {
454 MockControlFactory factory = mockClass.isInterface() ? _interfaceMockControlFactory
455 : _classMockControlFactory;
456
457 MockControl result = factory.newControl(mockClass);
458
459 addControl(result);
460
461 return result;
462 }
463
464 /**
465 * Accesses the control for a previously created mock object. Iterates over the list of managed
466 * controls until one is found whose mock object identity equals the mock object provided.
467 *
468 * @param mock
469 * object whose control is needed
470 * @return the corresponding MockControl if found
471 * @throws IllegalArgumentException
472 * if not found
473 * @since 1.1
474 */
475
476 protected MockControl getControl(Object mock)
477 {
478 Iterator i = _controls.iterator();
479 while (i.hasNext())
480 {
481 MockControl control = (MockControl) i.next();
482
483 if (control.getMock() == mock)
484 return control;
485 }
486
487 throw new IllegalArgumentException(mock
488 + " is not a mock object controlled by any registered MockControl instance.");
489 }
490
491 /**
492 * Invoked when training a mock object to set the Throwable for the most recently invoked
493 * method.
494 *
495 * @param mock
496 * the mock object being trained
497 * @param t
498 * the exception the object should throw when it replays
499 * @since 1.1
500 */
501 protected void setThrowable(Object mock, Throwable t)
502 {
503 getControl(mock).setThrowable(t);
504 }
505
506 /**
507 * Invoked when training a mock object to set the return value for the most recently invoked
508 * method. Overrides of this method exist to support a number of primitive types.
509 *
510 * @param mock
511 * the mock object being trained
512 * @param returnValue
513 * the value to return from the most recently invoked methods
514 * @since 1.1
515 */
516 protected void setReturnValue(Object mock, Object returnValue)
517 {
518 getControl(mock).setReturnValue(returnValue);
519 }
520
521 /**
522 * Invoked when training a mock object to set the return value for the most recently invoked
523 * method. Overrides of this method exist to support a number of primitive types.
524 *
525 * @param mock
526 * the mock object being trained
527 * @param returnValue
528 * the value to return from the most recently invoked methods
529 * @since 1.1
530 */
531 protected void setReturnValue(Object mock, long returnValue)
532 {
533 getControl(mock).setReturnValue(returnValue);
534 }
535
536 /**
537 * Invoked when training a mock object to set the return value for the most recently invoked
538 * method. Overrides of this method exist to support a number of primitive types.
539 *
540 * @param mock
541 * the mock object being trained
542 * @param returnValue
543 * the value to return from the most recently invoked methods
544 * @since 1.1
545 */
546 protected void setReturnValue(Object mock, float returnValue)
547 {
548 getControl(mock).setReturnValue(returnValue);
549 }
550
551 /**
552 * Invoked when training a mock object to set the return value for the most recently invoked
553 * method. Overrides of this method exist to support a number of primitive types.
554 *
555 * @param mock
556 * the mock object being trained
557 * @param returnValue
558 * the value to return from the most recently invoked methods
559 * @since 1.1
560 */
561 protected void setReturnValue(Object mock, double returnValue)
562 {
563 getControl(mock).setReturnValue(returnValue);
564 }
565
566 /**
567 * Invoked when training a mock object to set the return value for the most recently invoked
568 * method. Overrides of this method exist to support a number of primitive types.
569 *
570 * @param mock
571 * the mock object being trained
572 * @param returnValue
573 * the value to return from the most recently invoked methods
574 * @since 1.1
575 */
576 protected void setReturnValue(Object mock, boolean returnValue)
577 {
578 getControl(mock).setReturnValue(returnValue);
579 }
580
581 /**
582 * Adds the control to the list of managed controls used by {@link #replayControls()} and
583 * {@link #verifyControls()}.
584 */
585 protected void addControl(MockControl control)
586 {
587 _controls.add(control);
588 }
589
590 /**
591 * Convienience for invoking {@link #newControl(Class)} and then invoking
592 * {@link MockControl#getMock()} on the result.
593 */
594 protected Object newMock(Class mockClass)
595 {
596 return newControl(mockClass).getMock();
597 }
598
599 /**
600 * Invokes {@link MockControl#replay()} on all controls created by {@link #newControl(Class)}.
601 */
602 protected void replayControls()
603 {
604 Iterator i = _controls.iterator();
605 while (i.hasNext())
606 {
607 MockControl c = (MockControl) i.next();
608 c.replay();
609 }
610 }
611
612 /**
613 * Invokes {@link org.easymock.MockControl#verify()} and {@link MockControl#reset()} on all
614 * controls created by {@link #newControl(Class)}.
615 */
616
617 protected void verifyControls()
618 {
619 Iterator i = _controls.iterator();
620 while (i.hasNext())
621 {
622 MockControl c = (MockControl) i.next();
623 c.verify();
624 c.reset();
625 }
626 }
627
628 /**
629 * Invokes {@link org.easymock.MockControl#reset()} on all controls.
630 */
631
632 protected void resetControls()
633 {
634 Iterator i = _controls.iterator();
635 while (i.hasNext())
636 {
637 MockControl c = (MockControl) i.next();
638 c.reset();
639 }
640 }
641
642 /**
643 * @deprecated To be removed in 1.2. Use {@link #newLocation()} instead.
644 */
645 protected Location fabricateLocation(int line)
646 {
647 String path = "/" + getClass().getName().replace('.', '/');
648
649 Resource r = new ClasspathResource(getClassResolver(), path);
650
651 return new LocationImpl(r, line);
652 }
653
654 private int _line = 1;
655
656 /**
657 * Returns a new {@link Location} instance. The resource is the test class, and the line number
658 * increments by one from one for each invocation (thus each call will get a unique instance not
659 * equal to any previously obtained instance).
660 *
661 * @since 1.1
662 */
663 protected Location newLocation()
664 {
665 return fabricateLocation(_line++);
666 }
667
668 /**
669 * Returns a {@link DefaultClassResolver}. Repeated calls in the same test return the same
670 * value.
671 *
672 * @since 1.1
673 */
674
675 protected ClassResolver getClassResolver()
676 {
677 if (_classResolver == null)
678 _classResolver = new DefaultClassResolver();
679
680 return _classResolver;
681 }
682
683 protected boolean matches(String input, String pattern) throws Exception
684 {
685 setupMatcher();
686
687 Pattern compiled = _compiler.compile(pattern);
688
689 return _matcher.matches(input, compiled);
690 }
691
692 }