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 }