View Javadoc

1   /**
2    * 2009, Digitalis Informatica. All rights reserved. Distribuicao e Gestao de Informatica, Lda. Estrada de Paco de Arcos
3    * num.9 - Piso -1 2780-666 Paco de Arcos Telefone: (351) 21 4408990 Fax: (351) 21 4408999 http://www.digitalis.pt
4    */
5   
6   package pt.digitalis.dif.sanitycheck.manager;
7   
8   import java.util.ArrayList;
9   import java.util.Date;
10  import java.util.HashMap;
11  import java.util.List;
12  import java.util.Map;
13  import java.util.Map.Entry;
14  
15  import pt.digitalis.dif.controller.interfaces.IDIFContext;
16  import pt.digitalis.dif.ioc.DIFIoCRegistry;
17  import pt.digitalis.dif.sanitycheck.ExecutionResult;
18  import pt.digitalis.dif.sanitycheck.ISanityCheckTestSuite;
19  import pt.digitalis.dif.sanitycheck.SanityCheckResult;
20  import pt.digitalis.dif.sanitycheck.TestResult;
21  import pt.digitalis.dif.utils.logging.DIFLogger;
22  import pt.digitalis.log.LogLevel;
23  
24  /**
25   * @author Pedro Viegas <a href="mailto:pviegas@digitalis.pt">pviegas@digitalis.pt</a><br/>
26   * @created 14 de Jul de 2011
27   */
28  public class SanityCheckManager {
29  
30      /** Sanity check Logger Message Prefix */
31      protected static final String _SANITY_CHECK_LOGGER_PREFIX = "Sanity Check: ";
32  
33      /** Sanity check Logger Message */
34      protected static final String CIRCULAR_DEPENDENCY = _SANITY_CHECK_LOGGER_PREFIX
35              + "Circular dependency detected on TestSuite \"%1\" and \"%2\"";
36  
37      /** the test execution order according to the declared dependencies */
38      private static List<String> testSuiteExecutionOrder = null;
39  
40      /** the test suite ordered list after parsing all contributed ISanityCheckTestSuite */
41      static private Map<String, SanityCheckTestSuiteDefinition> testSuites = null;
42  
43      /**
44       * Inspector for the 'testSuiteExecutionOrder' attribute.
45       * 
46       * @return the testSuiteExecutionOrder value
47       */
48      private static List<String> getTestSuiteExecutionOrder()
49      {
50          if (testSuiteExecutionOrder == null)
51              initialize();
52  
53          return testSuiteExecutionOrder;
54      }
55  
56      /**
57       * Inspector for the 'testSuites' attribute.
58       * 
59       * @return the testSuites value
60       */
61      public static Map<String, SanityCheckTestSuiteDefinition> getTestSuites()
62      {
63          if (testSuites == null)
64              initialize();
65  
66          return testSuites;
67      }
68  
69      /**
70       * Initializes the manager. <br/>
71       * Will read all declared {@link ISanityCheckTestSuite} classes and mount the test suites and their attributes and
72       * dependencies.
73       */
74      private static void initialize()
75      {
76          // Test suite registry holder
77          testSuites = new HashMap<String, SanityCheckTestSuiteDefinition>();
78  
79          // Get all ISanityCheckTestSuite contributions from the IoC
80          Map<String, ISanityCheckTestSuite> suiteEntries = DIFIoCRegistry.getRegistry().getImplementationsMap(
81                  ISanityCheckTestSuite.class);
82  
83          // Add all to the registry attribute "testSuites"
84          for (Entry<String, ISanityCheckTestSuite> suiteEntry: suiteEntries.entrySet())
85          {
86              // Create a definition object, that will parse and determine execution for each test suite
87              if (suiteEntry.getValue() instanceof ISanityCheckTestSuite)
88                  testSuites.put(suiteEntry.getKey(),
89                          new SanityCheckTestSuiteDefinition(suiteEntry.getKey(), suiteEntry.getValue()));
90              else
91                  DIFLogger.getLogger().warn(
92                          _SANITY_CHECK_LOGGER_PREFIX + "Class \"" + suiteEntry.getValue().getClass().getSimpleName()
93                                  + "\" must implement " + ISanityCheckTestSuite.class.getSimpleName());
94          }
95  
96          // Determine the running order according to the determined dependencies
97          testSuiteExecutionOrder = new ArrayList<String>();
98  
99          // For all existing parse recursively through all dependencies
100         for (SanityCheckTestSuiteDefinition testSuite: testSuites.values())
101             if (!testSuiteExecutionOrder.contains(testSuite.getId()))
102                 // New testSuite. Add all dependencies which are not already declared
103                 testSuiteExecutionOrder = parseInheritedDependencies(testSuiteExecutionOrder, testSuite);
104     }
105 
106     /**
107      * Parses for all dependent test suites and their recursive dependencies.<br/>
108      * Will detect and ignore circular dependencies.<br/>
109      * <br/>
110      * The provided temporaryDependencies attribute is used to detect already declared testSuites and as such no need to
111      * crawl inside their dependency tree.
112      * 
113      * @param temporaryDependencies
114      *            currently already parsed dependencies
115      * @param stackDependencyParsing
116      *            a stack of the current dependencies in recursive analisis. Used to prevent circular dependencies
117      * @param suiteDefinition
118      *            the suite to check dependencies
119      * @return the current test suite recursive dependencies list
120      */
121     private static List<String> parseInheritedDependencies(List<String> temporaryDependencies,
122             List<String> stackDependencyParsing, SanityCheckTestSuiteDefinition suiteDefinition)
123     {
124         for (SanityCheckTestSuiteDefinition dependentSuide: suiteDefinition.getDependencies())
125         {
126             if (stackDependencyParsing.contains(dependentSuide.getId()))
127             {
128                 // The dependency is already referenced in the currently executing stack. Circular dependency.
129                 // Report and ignore
130                 DIFLogger.getLogger().warn(
131                         CIRCULAR_DEPENDENCY.replace("%1", suiteDefinition.getId())
132                                 .replace("%2", dependentSuide.getId()));
133 
134             }
135             else if (!temporaryDependencies.contains(dependentSuide.getId()))
136             {
137                 // Add recursive dependencies
138                 temporaryDependencies = parseInheritedDependencies(temporaryDependencies, dependentSuide);
139 
140                 // Add the current dependency
141                 temporaryDependencies.add(dependentSuide.getId());
142             }
143         }
144 
145         temporaryDependencies.add(suiteDefinition.getId());
146 
147         return temporaryDependencies;
148     }
149 
150     /**
151      * Parses for all dependent test suites and their recursive dependencies.<br/>
152      * Will detect and ignore circular dependencies.<br/>
153      * <br/>
154      * The provided temporaryDependencies attribute is used to detect already declared testSuites and as such no need to
155      * crawl inside their dependency tree.
156      * 
157      * @param temporaryDependencies
158      *            currently already parsed dependencies
159      * @param suiteDefinition
160      *            the suite to check dependencies
161      * @return the current test suite recursive dependencies list
162      */
163     private static List<String> parseInheritedDependencies(List<String> temporaryDependencies,
164             SanityCheckTestSuiteDefinition suiteDefinition)
165     {
166         return parseInheritedDependencies(temporaryDependencies, new ArrayList<String>(), suiteDefinition);
167     }
168 
169     /**
170      * Execute all tests
171      * 
172      * @param context
173      *            the current DIF context
174      * @return T if all tests ran successfully
175      */
176     public static boolean runAllTests(IDIFContext context)
177     {
178         return runAllTests(context, false);
179     }
180 
181     /**
182      * Execute all tests.
183      * 
184      * @param context
185      *            the current DIF context
186      * @param testOnlyErrors
187      *            the test only errors
188      * @return T if all tests ran successfully
189      */
190     public static boolean runAllTests(IDIFContext context, boolean testOnlyErrors)
191     {
192         boolean result = true;
193 
194         DIFLogger.getLogger().info(_SANITY_CHECK_LOGGER_PREFIX + "Starting Sanity Check tests...");
195 
196         // Execute all testSuites, by the correct order
197         for (String testSuiteID: getTestSuiteExecutionOrder())
198         {
199             SanityCheckTestSuiteDefinition testSuide = getTestSuites().get(testSuiteID);
200             ExecutionResult testsResult = runTestSuiteTests(testSuide, context).getExecutionsResult();
201             boolean testsOK = (testOnlyErrors ? (testsResult != ExecutionResult.FAILED)
202                     : (testsResult == ExecutionResult.PASSED));
203 
204             // First error, and log is not in INFO, so start tests message not rendered previously.
205             // Print warn heading...
206             if (!DIFLogger.getLogger().isInfoEnabled() && result && !testsOK)
207                 DIFLogger.getLogger().warn(_SANITY_CHECK_LOGGER_PREFIX + "Starting Sanity Check tests...");
208 
209             // Log according to result...
210             DIFLogger.getLogger().log(testsOK ? LogLevel.INFO : LogLevel.WARN,
211                     "    » " + testSuide.getName() + " (" + testSuiteID + "): " + testsResult.toString());
212 
213             result = result && testsOK;
214             if (testOnlyErrors && !result)
215             {
216                 break;
217             }
218         }
219 
220         // Feedback, according to the result
221         if (result)
222             DIFLogger.getLogger().info(_SANITY_CHECK_LOGGER_PREFIX + "All Sanity Checks ran with Success.");
223         else
224             DIFLogger.getLogger().warn(_SANITY_CHECK_LOGGER_PREFIX + "Errors while running Sanity Checks.");
225 
226         return result;
227     }
228 
229     /**
230      * Run all tests of the {@link ISanityCheckTestSuite}
231      * 
232      * @param suiteDefinition
233      *            the test suite to run the tests
234      * @param context
235      *            the current DIF context
236      * @return the executing result
237      */
238     private static SanityCheckResult runTestSuiteTests(SanityCheckTestSuiteDefinition suiteDefinition,
239             IDIFContext context)
240     {
241         suiteDefinition.setExecutionResult(new SanityCheckResult(suiteDefinition, ExecutionResult.EXECUTING));
242         suiteDefinition.getExecutionResult().setStartTime(new Date());
243 
244         try
245         {
246             suiteDefinition.runTestsSetup();
247 
248             boolean warnings = false;
249             boolean errors = false;
250 
251             // Run all tests, adding each result to the execution result...
252             for (TestMethodDefinition test: suiteDefinition.getTestMethods())
253             {
254                 TestResult testResult = test.run(context);
255 
256                 // Set the test name if empty to the test method
257                 if (testResult.getName() == null)
258                     testResult.setName(test.getName());
259 
260                 errors = errors || testResult.getExecutionResult() == ExecutionResult.FAILED;
261                 warnings = warnings || testResult.getExecutionResult() == ExecutionResult.WARNING;
262 
263                 suiteDefinition.getExecutionResult().addTestResult(testResult);
264             }
265 
266             suiteDefinition.runTestsFinalize();
267 
268             suiteDefinition.getExecutionResult().setExecutionsResult(
269                     errors ? ExecutionResult.FAILED : warnings ? ExecutionResult.WARNING : ExecutionResult.PASSED);
270         }
271         catch (Exception e)
272         {
273             e.printStackTrace();
274 
275             suiteDefinition.getExecutionResult().setExecutionsResult(ExecutionResult.FAILED);
276             suiteDefinition.getExecutionResult().setExecutionError(e.getMessage());
277         }
278         suiteDefinition.getExecutionResult().setEndTime(new Date());
279 
280         return suiteDefinition.getExecutionResult();
281     }
282 }