View Javadoc
1   /*
2    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
3    * The software in this package is published under the terms of the CPAL v1.0
4    * license, a copy of which has been included with this distribution in the
5    * LICENSE.txt file.
6    */
7   package org.mule.tck.junit4;
8   
9   import org.mule.RequestContext;
10  import org.mule.tck.junit4.rule.WarningTimeout;
11  import org.mule.util.ClassUtils;
12  import org.mule.util.IOUtils;
13  import org.mule.util.MuleUrlStreamHandlerFactory;
14  import org.mule.util.StringMessageUtils;
15  import org.mule.util.StringUtils;
16  import org.mule.util.SystemUtils;
17  
18  import java.io.IOException;
19  import java.io.InputStream;
20  import java.net.URL;
21  import java.net.URLClassLoader;
22  import java.util.Iterator;
23  import java.util.Map;
24  
25  import org.apache.commons.collections.IteratorUtils;
26  import org.apache.commons.collections.Predicate;
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.hamcrest.BaseMatcher;
30  import org.hamcrest.Description;
31  import org.junit.After;
32  import org.junit.AfterClass;
33  import org.junit.Before;
34  import org.junit.Rule;
35  import org.junit.rules.TestName;
36  import org.junit.rules.TestRule;
37  import org.junit.rules.Timeout;
38  
39  import static org.junit.Assume.assumeThat;
40  
41  /**
42   * <code>AbstractMuleTestCase</code> is a base class for Mule test cases. This
43   * implementation provides services to test code for creating mock and test
44   * objects.
45   */
46  public abstract class AbstractMuleTestCase
47  {
48  
49      public static final int DEFAULT_TEST_TIMEOUT_SECS = 60;
50  
51      public static final String TEST_TIMEOUT_SYSTEM_PROPERTY = "mule.test.timeoutSecs";
52  
53      /**
54       * Indicates whether the text boxes will be logged when starting each test case.
55       */
56      private static final boolean verbose;
57  
58      /**
59       * Indicates if the current test class was excluded using the mule test
60       * exclusion files. Test are executed sequentially, so is not required to
61       * maintain a list of classes.
62       */
63      private static Boolean excluded = null;
64  
65      static
66      {
67          String muleOpts = SystemUtils.getenv("MULE_TEST_OPTS");
68          if (StringUtils.isNotBlank(muleOpts))
69          {
70              Map<String, String> parsedOpts = SystemUtils.parsePropertyDefinitions(muleOpts);
71              String optVerbose = parsedOpts.get("mule.verbose");
72              verbose = Boolean.valueOf(optVerbose);
73          }
74          else
75          {
76              verbose = true;
77          }
78  
79          // register the custom UrlStreamHandlerFactory.
80          MuleUrlStreamHandlerFactory.installUrlStreamHandlerFactory();
81      }
82  
83      protected final transient Log logger = LogFactory.getLog(this.getClass());
84  
85      /**
86       * Should be set to a string message describing any prerequisites not met.
87       */
88      private boolean offline = "true".equalsIgnoreCase(System.getProperty("org.mule.offline"));
89  
90      private int testTimeoutSecs = getTimeoutSystemProperty();
91  
92      @Rule
93      public TestName name = new TestName();
94  
95      @Rule
96      public TestRule globalTimeout = createTestTimeoutRule();
97  
98      public AbstractMuleTestCase()
99      {
100         if (excluded == null)
101         {
102             excluded = isTestIncludedInExclusionFile(this);
103         }
104     }
105 
106     /**
107      * Creates the timeout rule that will be used to run the test.
108      *
109      * @return the rule used to check for test execution timeouts.
110      */
111     protected TestRule createTestTimeoutRule()
112     {
113         int millisecondsTimeout = getTestTimeoutSecs() * 1000;
114 
115         if (isFailOnTimeout())
116         {
117             return new Timeout(millisecondsTimeout);
118         }
119         else
120         {
121             return new WarningTimeout(millisecondsTimeout);
122         }
123     }
124 
125     /**
126      * Reads the mule-exclusion file for the current test class and
127      * @param test
128      */
129     protected boolean isTestIncludedInExclusionFile(AbstractMuleTestCase test)
130     {
131         boolean result = false;
132 
133         final String name = test.getClass().getName();
134         try
135         {
136             // We find the physical classpath root URL of the test class and
137             // use that to find the correct resource. Works fine everywhere,
138             // regardless of classloaders. See MULE-2414
139             URL classUrl = ClassUtils.getClassPathRoot(test.getClass());
140             URLClassLoader tempClassLoader = new URLClassLoader(new URL[] {classUrl});
141             URL fileUrl = tempClassLoader.getResource("mule-test-exclusions.txt");
142             if (fileUrl != null)
143             {
144                 InputStream in = null;
145                 try
146                 {
147                     in = fileUrl.openStream();
148 
149                     // this iterates over all lines in the exclusion file
150                     Iterator<?> lines = IOUtils.lineIterator(in, "UTF-8");
151 
152                     // ..and this finds non-comments that match the test case name
153                     result = IteratorUtils.filteredIterator(lines, new Predicate()
154                     {
155                         public boolean evaluate(Object object)
156                         {
157                             return StringUtils.equals(name, StringUtils.trimToEmpty((String) object));
158                         }
159                     }).hasNext();
160                 }
161                 finally
162                 {
163                     IOUtils.closeQuietly(in);
164                 }
165             }
166         }
167         catch (IOException ioex)
168         {
169             // ignore
170         }
171 
172         return result;
173     }
174 
175     /**
176      * Defines the number of seconds that a test has in order to run before
177      * throwing a timeout. If the property if not defined then uses the
178      * <code>DEFAULT_MULE_TEST_TIMEOUT_SECS</code> constant.
179      *
180      * @return the timeout value expressed in seconds
181      */
182     protected int getTimeoutSystemProperty()
183     {
184         String timeoutString = System.getProperty(TEST_TIMEOUT_SYSTEM_PROPERTY, null);
185         if (timeoutString == null)
186         {
187             // unix style: MULE_TEST_TIMEOUTSECS
188             String variableName = TEST_TIMEOUT_SYSTEM_PROPERTY.toUpperCase().replace(".", "_");
189             timeoutString = System.getenv(variableName);
190         }
191 
192         int result = DEFAULT_TEST_TIMEOUT_SECS;
193         if (timeoutString != null)
194         {
195             try
196             {
197                 result = Integer.parseInt(timeoutString);
198             }
199             catch (NumberFormatException e)
200             {
201                 // Uses the default value
202             }
203         }
204 
205         return result;
206     }
207 
208     /**
209      * Subclasses can override this method to skip the execution of the entire test class.
210      *
211      * @return <code>true</code> if the test class should not be run.
212      */
213     protected boolean isDisabledInThisEnvironment()
214     {
215         return false;
216     }
217 
218     /**
219      * Indicates whether this test has been explicitly disabled through the configuration
220      * file loaded by TestInfo.
221      *
222      * @return whether the test has been explicitly disabled
223      */
224     protected boolean isExcluded()
225     {
226         return excluded;
227     }
228 
229     /**
230      * Should this test run?
231      *
232      * @param testMethodName name of the test method
233      * @return whether the test should execute in the current environment
234      */
235     protected boolean isDisabledInThisEnvironment(String testMethodName)
236     {
237         return false;
238     }
239 
240     public boolean isOffline(String method)
241     {
242         if (offline)
243         {
244             logger.warn(StringMessageUtils.getBoilerPlate(
245                     "Working offline cannot run test: " + method, '=', 80));
246         }
247 
248         return offline;
249     }
250 
251     /**
252      * Defines the timeout in seconds that will be used to run the test.
253      *
254      * @return the timeout in seconds
255      */
256     public int getTestTimeoutSecs()
257     {
258         return testTimeoutSecs;
259     }
260 
261     @Before
262     public final void initializeMuleTest()
263     {
264         printTestHeader();
265         skipTestWhenExcluded();
266         skipTestWhenDisabledInCurrentEnvironment();
267     }
268 
269     private void printTestHeader()
270     {
271         if (verbose)
272         {
273             System.out.println(StringMessageUtils.getBoilerPlate(getTestHeader(), '=', 80));
274         }
275     }
276 
277     protected String getTestHeader()
278     {
279         return "Testing: " + name.getMethodName();
280     }
281 
282     private void skipTestWhenExcluded()
283     {
284         assumeThat(this, new BaseMatcher<AbstractMuleTestCase>()
285         {
286             public boolean matches(Object o)
287             {
288                 return !isExcluded();
289             }
290 
291             public void describeTo(Description description)
292             {
293                 description.appendText("Test " + name.getMethodName() + " is excluded");
294             }
295         });
296     }
297 
298     private void skipTestWhenDisabledInCurrentEnvironment()
299     {
300         assumeThat(this, new BaseMatcher<AbstractMuleTestCase>()
301         {
302             public boolean matches(Object o)
303             {
304                 return !(isDisabledInThisEnvironment() || isDisabledInThisEnvironment(name.getMethodName()));
305             }
306 
307             public void describeTo(Description description)
308             {
309                 description.appendText("Test " + name.getMethodName() + " disabled in this environment");
310             }
311         });
312     }
313 
314     /**
315      * Indicates whether the test should fail when a timeout is reached.
316      * <p/>
317      * This feature was added to support old test cases that depend on 3rd-party
318      * resources such as a public web service. In such cases it may be desirable
319      * to not fail the test upon timeout but rather to simply log a warning.
320      *
321      * @return true if it must fail on timeout and false otherwise. Default value
322      *         is true.
323      */
324     protected boolean isFailOnTimeout()
325     {
326         return true;
327     }
328 
329     @After
330     public final void clearRequestContext()
331     {
332         RequestContext.clear();
333     }
334 
335     @AfterClass
336     public static final void clearExcludedFlag()
337     {
338         excluded = null;
339     }
340 }