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
015package org.apache.hivemind.test;
016
017import java.net.URL;
018import java.util.ArrayList;
019import java.util.Iterator;
020import java.util.List;
021import java.util.Locale;
022
023import junit.framework.AssertionFailedError;
024import junit.framework.TestCase;
025
026import org.apache.hivemind.ApplicationRuntimeException;
027import org.apache.hivemind.ClassResolver;
028import org.apache.hivemind.Location;
029import org.apache.hivemind.ModuleDescriptorProvider;
030import org.apache.hivemind.Registry;
031import org.apache.hivemind.Resource;
032import org.apache.hivemind.impl.DefaultClassResolver;
033import org.apache.hivemind.impl.LocationImpl;
034import org.apache.hivemind.impl.RegistryBuilder;
035import org.apache.hivemind.impl.XmlModuleDescriptorProvider;
036import org.apache.hivemind.internal.ser.ServiceSerializationHelper;
037import org.apache.hivemind.util.ClasspathResource;
038import org.apache.hivemind.util.PropertyUtils;
039import org.apache.hivemind.util.URLResource;
040import org.apache.log4j.Level;
041import org.apache.log4j.LogManager;
042import org.apache.log4j.Logger;
043import org.apache.log4j.spi.LoggingEvent;
044import org.apache.oro.text.regex.Pattern;
045import org.apache.oro.text.regex.Perl5Compiler;
046import org.apache.oro.text.regex.Perl5Matcher;
047import org.easymock.MockControl;
048import 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 */
056public 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}