View Javadoc
1   package org.apache.maven.plugin.surefire.runorder;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  
23  import org.apache.maven.surefire.report.ReportEntry;
24  
25  import java.io.BufferedReader;
26  import java.io.File;
27  import java.io.FileNotFoundException;
28  import java.io.FileOutputStream;
29  import java.io.FileReader;
30  import java.io.IOException;
31  import java.io.PrintWriter;
32  import java.io.Reader;
33  import java.util.ArrayList;
34  import java.util.Comparator;
35  import java.util.HashMap;
36  import java.util.List;
37  import java.util.Map;
38  import java.util.concurrent.ConcurrentHashMap;
39  import java.util.regex.Matcher;
40  import java.util.regex.Pattern;
41  
42  import static java.util.Collections.sort;
43  import static org.apache.maven.plugin.surefire.runorder.RunEntryStatistics.fromReportEntry;
44  import static org.apache.maven.plugin.surefire.runorder.RunEntryStatistics.fromString;
45  
46  /**
47   * @author Kristian Rosenvold
48   */
49  public final class RunEntryStatisticsMap
50  {
51      private final Map<String, RunEntryStatistics> runEntryStatistics;
52  
53      public RunEntryStatisticsMap( Map<String, RunEntryStatistics> runEntryStatistics )
54      {
55          this.runEntryStatistics = new ConcurrentHashMap<String, RunEntryStatistics>( runEntryStatistics );
56      }
57  
58      public RunEntryStatisticsMap()
59      {
60          runEntryStatistics = new ConcurrentHashMap<String, RunEntryStatistics>();
61      }
62  
63      public static RunEntryStatisticsMap fromFile( File file )
64      {
65          if ( file.exists() )
66          {
67              try
68              {
69                  return fromReader( new FileReader( file ) );
70              }
71              catch ( FileNotFoundException e )
72              {
73                  throw new RuntimeException( e );
74              }
75              catch ( IOException e )
76              {
77                  throw new RuntimeException( e );
78              }
79          }
80          else
81          {
82              return new RunEntryStatisticsMap();
83          }
84      }
85  
86      static RunEntryStatisticsMap fromReader( Reader fileReader )
87          throws IOException
88      {
89          Map<String, RunEntryStatistics> result = new HashMap<String, RunEntryStatistics>();
90          BufferedReader bufferedReader = new BufferedReader( fileReader );
91          String line = bufferedReader.readLine();
92          while ( line != null )
93          {
94              if ( !line.startsWith( "#" ) )
95              {
96                  final RunEntryStatistics stats = fromString( line );
97                  result.put( stats.getTestName(), stats );
98              }
99              line = bufferedReader.readLine();
100         }
101         return new RunEntryStatisticsMap( result );
102     }
103 
104     public void serialize( File file )
105         throws FileNotFoundException
106     {
107         FileOutputStream fos = new FileOutputStream( file );
108         PrintWriter printWriter = new PrintWriter( fos );
109         try
110         {
111             List<RunEntryStatistics> items = new ArrayList<RunEntryStatistics>( runEntryStatistics.values() );
112             sort( items, new RunCountComparator() );
113             for ( RunEntryStatistics item : items )
114             {
115                 printWriter.println( item.toString() );
116             }
117             printWriter.flush();
118         }
119         finally
120         {
121             printWriter.close();
122         }
123     }
124 
125     public RunEntryStatistics findOrCreate( ReportEntry reportEntry )
126     {
127         final RunEntryStatistics item = runEntryStatistics.get( reportEntry.getName() );
128         return item != null ? item : fromReportEntry( reportEntry );
129     }
130 
131     public RunEntryStatistics createNextGeneration( ReportEntry reportEntry )
132     {
133         final RunEntryStatistics newItem = findOrCreate( reportEntry );
134         final Integer elapsed = reportEntry.getElapsed();
135         return newItem.nextGeneration( elapsed != null ? elapsed : 0 );
136     }
137 
138     public RunEntryStatistics createNextGenerationFailure( ReportEntry reportEntry )
139     {
140         final RunEntryStatistics newItem = findOrCreate( reportEntry );
141         final Integer elapsed = reportEntry.getElapsed();
142         return newItem.nextGenerationFailure( elapsed != null ? elapsed : 0 );
143     }
144 
145     public void add( RunEntryStatistics item )
146     {
147         runEntryStatistics.put( item.getTestName(), item );
148     }
149 
150     static final class RunCountComparator
151         implements Comparator<RunEntryStatistics>
152     {
153         @Override
154         public int compare( RunEntryStatistics o, RunEntryStatistics o1 )
155         {
156             int runtime = o.getSuccessfulBuilds() - o1.getSuccessfulBuilds();
157             return runtime == 0 ? o.getRunTime() - o1.getRunTime() : runtime;
158         }
159     }
160 
161     public List<Class<?>> getPrioritizedTestsClassRunTime( List<Class<?>> testsToRun, int threadCount )
162     {
163         List<PrioritizedTest> prioritizedTests = getPrioritizedTests( testsToRun, new TestRuntimeComparator() );
164         ThreadedExecutionScheduler threadedExecutionScheduler = new ThreadedExecutionScheduler( threadCount );
165         for ( Object prioritizedTest1 : prioritizedTests )
166         {
167             threadedExecutionScheduler.addTest( (PrioritizedTest) prioritizedTest1 );
168         }
169 
170         return threadedExecutionScheduler.getResult();
171     }
172 
173     public List<Class<?>> getPrioritizedTestsByFailureFirst( List<Class<?>> testsToRun )
174     {
175         List<PrioritizedTest> prioritizedTests = getPrioritizedTests( testsToRun, new LeastFailureComparator() );
176         return transformToClasses( prioritizedTests );
177     }
178 
179     private List<PrioritizedTest> getPrioritizedTests( List<Class<?>> testsToRun,
180                                                        Comparator<Priority> priorityComparator )
181     {
182         Map classPriorities = getPriorities( priorityComparator );
183 
184         List<PrioritizedTest> tests = new ArrayList<PrioritizedTest>();
185         for ( Class<?> clazz : testsToRun )
186         {
187             Priority pri = (Priority) classPriorities.get( clazz.getName() );
188             if ( pri == null )
189             {
190                 pri = Priority.newTestClassPriority( clazz.getName() );
191             }
192             PrioritizedTest prioritizedTest = new PrioritizedTest( clazz, pri );
193             tests.add( prioritizedTest );
194         }
195         sort( tests, new PrioritizedTestComparator() );
196         return tests;
197     }
198 
199     private List<Class<?>> transformToClasses( List<PrioritizedTest> tests )
200     {
201         List<Class<?>> result = new ArrayList<Class<?>>();
202         for ( PrioritizedTest test : tests )
203         {
204             result.add( test.getClazz() );
205         }
206         return result;
207     }
208 
209     private Map getPriorities( Comparator<Priority> priorityComparator )
210     {
211         Map<String, Priority> priorities = new HashMap<String, Priority>();
212         for ( Object o : runEntryStatistics.keySet() )
213         {
214             String testNames = (String) o;
215             String clazzName = extractClassName( testNames );
216             Priority priority = priorities.get( clazzName );
217             if ( priority == null )
218             {
219                 priority = new Priority( clazzName );
220                 priorities.put( clazzName, priority );
221             }
222 
223             RunEntryStatistics itemStat = runEntryStatistics.get( testNames );
224             priority.addItem( itemStat );
225         }
226 
227         List<Priority> items = new ArrayList<Priority>( priorities.values() );
228         sort( items, priorityComparator );
229         Map<String, Priority> result = new HashMap<String, Priority>();
230         int i = 0;
231         for ( Priority pri : items )
232         {
233             pri.setPriority( i++ );
234             result.put( pri.getClassName(), pri );
235         }
236         return result;
237     }
238 
239     static final class PrioritizedTestComparator
240         implements Comparator<PrioritizedTest>
241     {
242         @Override
243         public int compare( PrioritizedTest o, PrioritizedTest o1 )
244         {
245             return o.getPriority() - o1.getPriority();
246         }
247     }
248 
249     static final class TestRuntimeComparator
250         implements Comparator<Priority>
251     {
252         @Override
253         public int compare( Priority o, Priority o1 )
254         {
255             return o1.getTotalRuntime() - o.getTotalRuntime();
256         }
257     }
258 
259     static final class LeastFailureComparator
260         implements Comparator<Priority>
261     {
262         @Override
263         public int compare( Priority o, Priority o1 )
264         {
265             return o.getMinSuccessRate() - o1.getMinSuccessRate();
266         }
267     }
268 
269 
270     private static final Pattern PARENS = Pattern.compile( "^" + "[^\\(\\)]+" //non-parens
271                                                                + "\\((" // then an open-paren (start matching a group)
272                                                                + "[^\\\\(\\\\)]+" //non-parens
273                                                                + ")\\)" + "$" ); // then a close-paren (end group match)
274 
275     String extractClassName( String displayName )
276     {
277         Matcher m = PARENS.matcher( displayName );
278         return m.find() ? m.group( 1 ) : displayName;
279     }
280 }