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    }