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 }