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