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    }