View Javadoc

1   /*
2    * $Id: MuleAbstractConnectableTestCase.java 19191 2010-08-25 21:05:23Z tcarlson $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
5    *
6    * The software in this package is published under the terms of the CPAL v1.0
7    * license, a copy of which has been included with this distribution in the
8    * LICENSE.txt file.
9    */
10  
11  package org.mule.transport;
12  
13  import org.mule.api.MuleContext;
14  import org.mule.api.MuleException;
15  import org.mule.api.context.WorkManager;
16  import org.mule.api.endpoint.ImmutableEndpoint;
17  import org.mule.api.retry.RetryCallback;
18  import org.mule.api.retry.RetryPolicyTemplate;
19  import org.mule.endpoint.MuleEndpointURI;
20  import org.mule.transport.MuleAbstractConnectableTestCase.MethodInvocation.MethodPart;
21  
22  import java.util.List;
23  import java.util.Vector;
24  import java.util.concurrent.atomic.AtomicBoolean;
25  
26  import edu.umd.cs.mtc.MultithreadedTestCase;
27  import edu.umd.cs.mtc.TestFramework;
28  
29  import org.apache.commons.lang.builder.EqualsBuilder;
30  import org.apache.commons.lang.builder.HashCodeBuilder;
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.junit.After;
34  import org.junit.Before;
35  import org.junit.Ignore;
36  import org.junit.Test;
37  import org.mockito.invocation.InvocationOnMock;
38  import org.mockito.stubbing.Answer;
39  
40  import static org.junit.Assert.fail;
41  import static org.mockito.Matchers.any;
42  import static org.mockito.Mockito.mock;
43  import static org.mockito.Mockito.when;
44  
45  /**
46   * This test tests class {@link AbstractConnectable} but its name starts with "Mule"
47   * because there is an exclusion rule in parent pom for test classes that have their
48   * names starting with "Abstract".
49   */
50  public class MuleAbstractConnectableTestCase
51  {
52      /**
53       * The logger used for this class
54       */
55      static Log log = LogFactory.getLog(MuleAbstractConnectableTestCase.class);
56  
57      @Test
58      public void testStartRethrowsMuleExceptionCorrectly() throws Exception
59      {
60          final MuleException someMuleException = mock(MuleException.class);
61          AbstractConnectable connectable = new AbstractConnectable(createDummyEndpoint())
62          {
63              @Override
64              protected void doStart() throws MuleException
65              {
66                  throw someMuleException;
67              }
68  
69              @Override
70              protected WorkManager getWorkManager()
71              {
72                  return null;
73              }
74  
75              @Override
76              protected ConnectableLifecycleManager createLifecycleManager()
77              {
78                  return new ConnectableLifecycleManager("test", this);
79              }
80          };
81          connectable.initialise();
82          connectable.connect();
83          try
84          {
85              connectable.start();
86              fail("Should have thrown a " + MuleException.class.getSimpleName());
87          }
88          catch (MuleException caughtException)
89          {
90              assertExceptionIsInCaughtException(someMuleException, caughtException);
91          }
92      }
93  
94      /**
95       * This test tests that start method is thread safe and that
96       * {@link AbstractConnectable#doStart()} is always called making sure that the
97       * connectable is {@link AbstractConnectable#isConnected() connected}.
98       * <p>
99       * To make multithreaded test easier it uses a library called <a
100      * href="http://www.cs.umd.edu/projects/PL/multithreadedtc/overview.html"
101      * >MultithreadedTC</a>. You will perhaps go and read that link if you want to
102      * understand how this test works.
103      * 
104      * @throws Throwable
105      */
106     @Ignore
107     @Test
108     public void testStartIsThreadSafe() throws Throwable
109     {
110         TestFramework.runOnce(new AbstractConnectableMultithreaded());
111     }
112 
113     /**
114      * This inner class will do the work of
115      * {@link MuleAbstractConnectableTestCase#testStartIsThreadSafe()
116      * testStartIsThreadSafe()}.
117      */
118     @Ignore
119     class AbstractConnectableMultithreaded extends MultithreadedTestCase
120     {
121         private volatile AbstractConnectableForTest connectable;
122 
123         /**
124          * This is called before any of the thread* methods are called. Pretty much
125          * like a {@link Before} method in JUnit 4.
126          */
127         @Override
128         public void initialize()
129         {
130             try
131             {
132                 ImmutableEndpoint endpoint = createDummyEndpoint();
133 
134                 connectable = new AbstractConnectableForTest(endpoint);
135                 connectable.initialise();
136             }
137             catch (Exception e)
138             {
139                 throw new RuntimeException(e);
140             }
141         }
142 
143         /**
144          * This will be called by the <a
145          * href="http://www.cs.umd.edu/projects/PL/multithreadedtc/overview.html"
146          * >MultithreadedTC</a> framework with an independent thread.
147          * 
148          * @throws Exception on unexpected error.
149          */
150         public void thread1() throws Exception
151         {
152             connectable.start();
153         }
154 
155         /**
156          * @see #thread1()
157          * @throws Exception on unexpected error
158          */
159         public void thread2() throws Exception
160         {
161             // waiting for first tick means that thread1 will work on tick 0 and
162             // after it blocks then this one will start.
163             waitForTick(1);
164             connectable.start();
165         }
166 
167         /**
168          * This is called after all the thread* methods finish. Pretty much like an
169          * {@link After} method in JUnit 4.
170          */
171         @Override
172         public void finish()
173         {
174             for (MethodInvocation methodInvocation : connectable.methodInvocations)
175             {
176                 log.debug(methodInvocation);
177             }
178 
179             int i = 0;
180             assertEquals("doConnect", connectable.methodInvocations.get(i++).getMethodName());
181             assertEquals("doConnect", connectable.methodInvocations.get(i++).getMethodName());
182             assertEquals("doStart", connectable.methodInvocations.get(i++).getMethodName());
183             assertEquals("doStart", connectable.methodInvocations.get(i++).getMethodName());
184         }
185 
186         /**
187          * This is a specific implementation of {@link AbstractConnectable} for this
188          * test that will only implement {@link #doConnect()} and {@link #doStart()}
189          * methods.
190          * <p>
191          * The implementation of those methods simulate a long {@link #doConnect()}
192          * execution that makes the other thread attempt to execute
193          * {@link #doStart()}. They have also code that validates that
194          * {@link #doStart()} is never called before {@link #doConnect()} has
195          * finished.
196          */
197         @Ignore
198         class AbstractConnectableForTest extends AbstractConnectable
199         {
200             private final AtomicBoolean doConnectCalled = new AtomicBoolean();
201             private final AtomicBoolean doStartCalled = new AtomicBoolean();
202 
203             /**
204              * This list will hold reference to each of the calls to the methods
205              * {@link #doConnect()} and {@link #doStart()}. We use a {@link Vector}
206              * because it will be accessed by different threads.
207              */
208             List<MethodInvocation> methodInvocations = new Vector<MethodInvocation>();
209 
210             public AbstractConnectableForTest(ImmutableEndpoint endpoint)
211             {
212                 super(endpoint);
213             }
214 
215             @Override
216             protected WorkManager getWorkManager()
217             {
218                 return null;
219             }
220 
221             @Override
222             protected ConnectableLifecycleManager createLifecycleManager()
223             {
224                 return new ConnectableLifecycleManager("test", this);
225             }
226 
227             @Override
228             protected void doConnect() throws Exception
229             {
230                 methodInvocations.add(new MethodInvocation(Thread.currentThread(), "doConnect",
231                     MethodPart.BEGINNING));
232                 assertTrue(doConnectCalled.compareAndSet(false, true));
233                 assertFalse(doStartCalled.get());
234 
235                 // This method is called by thread1 in the test and waiting
236                 // for tick 2 will make thread 2 execute during tick 1, attempting
237                 // to call doStart(). Thread 2 should be then blocked waiting on a
238                 // monitor until AbstractConnectable.connected is true, which
239                 // triggers tick 2 and thread 1 will resume its execution,
240                 // finishing the exection of this method.
241                 waitForTick(2);
242 
243                 assertFalse(doStartCalled.get());
244 
245                 methodInvocations.add(new MethodInvocation(Thread.currentThread(), "doConnect",
246                     MethodPart.END));
247             }
248 
249             @Override
250             protected void doStart() throws MuleException
251             {
252                 methodInvocations.add(new MethodInvocation(Thread.currentThread(), "doStart",
253                     MethodPart.BEGINNING));
254                 assertTrue(doStartCalled.compareAndSet(false, true));
255                 assertTrue(doConnectCalled.get());
256                 methodInvocations.add(new MethodInvocation(Thread.currentThread(), "doStart", MethodPart.END));
257             }
258 
259         }
260     }
261 
262     /**
263      * This class just represent a method invocation that allow keeping track of the
264      * order in which calls are made by different threads.
265      */
266     @org.junit.Ignore
267     static class MethodInvocation
268     {
269         @Ignore
270         static enum MethodPart
271         {
272             BEGINNING, END
273         }
274 
275         private final Thread thread;
276         private final String methodName;
277         private final MethodPart methodPart;
278 
279         public MethodInvocation(Thread thread, String methodName, MethodPart methodPart)
280         {
281             this.thread = thread;
282             this.methodName = methodName;
283             this.methodPart = methodPart;
284         }
285 
286         public Thread getThread()
287         {
288             return thread;
289         }
290 
291         public String getMethodName()
292         {
293             return methodName;
294         }
295 
296         public MethodPart getMethodPart()
297         {
298             return methodPart;
299         }
300 
301         @Override
302         public boolean equals(Object obj)
303         {
304             if (this == obj)
305             {
306                 return true;
307             }
308             else if (obj == null || obj.getClass() != this.getClass())
309             {
310                 return false;
311             }
312             else
313             {
314                 MethodInvocation other = (MethodInvocation) obj;
315                 return new EqualsBuilder().append(this.thread, other.thread).append(this.methodName,
316                     other.methodName).append(this.methodPart, other.methodPart).isEquals();
317             }
318         }
319 
320         @Override
321         public int hashCode()
322         {
323             return new HashCodeBuilder().append(this.thread)
324                 .append(this.methodName)
325                 .append(this.methodPart)
326                 .toHashCode();
327         }
328 
329         @Override
330         public String toString()
331         {
332             return "Thread " + this.thread + " passing through " + this.methodName + "() at the "
333                    + this.methodPart;
334         }
335     }
336 
337     private void assertExceptionIsInCaughtException(MuleException someMuleException, MuleException caughtException)
338     {
339         boolean found = false;
340         Throwable candidate = caughtException;
341         while (candidate != null)
342         {
343             if (someMuleException.equals(candidate))
344             {
345                 found = true;
346                 break;
347             }
348             
349             candidate = candidate.getCause();
350         }
351         
352         if (found == false)
353         {
354             fail();
355         }
356     }
357     
358     /**
359      * @return an dummy implementation of {@link ImmutableEndpoint} suitable for this
360      *         test.
361      * @throws Exception
362      */
363     ImmutableEndpoint createDummyEndpoint() throws Exception
364     {
365         ImmutableEndpoint endpoint = mock(ImmutableEndpoint.class);
366         MuleContext muleContext = mock(MuleContext.class);
367         when(endpoint.getEndpointURI()).thenReturn(new MuleEndpointURI("http://dummy.endpoint/", muleContext));
368         AbstractConnector connector = mock(AbstractConnector.class);
369         when(endpoint.getConnector()).thenReturn(connector);
370 
371         RetryPolicyTemplate retryPolicyTemplate = mock(RetryPolicyTemplate.class);
372         when(endpoint.getRetryPolicyTemplate()).thenReturn(retryPolicyTemplate);
373         when(retryPolicyTemplate.execute(any(RetryCallback.class), any(WorkManager.class))).thenAnswer(
374             new Answer<Object>()
375             {
376                 public Object answer(InvocationOnMock invocation) throws Throwable
377                 {
378                     RetryCallback retryCallback = (RetryCallback) invocation.getArguments()[0];
379                     retryCallback.doWork(null);
380                     return null;
381                 }
382             });
383 
384         return endpoint;
385     }
386 }