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         public int compare( RunEntryStatistics o, RunEntryStatistics o1 )
154         {
155             int runtime = o.getSuccessfulBuilds() - o1.getSuccessfulBuilds();
156             return runtime == 0 ? o.getRunTime() - o1.getRunTime() : runtime;
157         }
158     }
159 
160     public List<Class<?>> getPrioritizedTestsClassRunTime( List<Class<?>> testsToRun, int threadCount )
161     {
162         List<PrioritizedTest> prioritizedTests = getPrioritizedTests( testsToRun, new TestRuntimeComparator() );
163         ThreadedExecutionScheduler threadedExecutionScheduler = new ThreadedExecutionScheduler( threadCount );
164         for ( Object prioritizedTest1 : prioritizedTests )
165         {
166             threadedExecutionScheduler.addTest( (PrioritizedTest) prioritizedTest1 );
167         }
168 
169         return threadedExecutionScheduler.getResult();
170     }
171 
172     public List<Class<?>> getPrioritizedTestsByFailureFirst( List<Class<?>> testsToRun )
173     {
174         List<PrioritizedTest> prioritizedTests = getPrioritizedTests( testsToRun, new LeastFailureComparator() );
175         return transformToClasses( prioritizedTests );
176     }
177 
178     private List<PrioritizedTest> getPrioritizedTests( List<Class<?>> testsToRun,
179                                                        Comparator<Priority> priorityComparator )
180     {
181         Map classPriorities = getPriorities( priorityComparator );
182 
183         List<PrioritizedTest> tests = new ArrayList<PrioritizedTest>();
184         for ( Class<?> clazz : testsToRun )
185         {
186             Priority pri = (Priority) classPriorities.get( clazz.getName() );
187             if ( pri == null )
188             {
189                 pri = Priority.newTestClassPriority( clazz.getName() );
190             }
191             PrioritizedTest prioritizedTest = new PrioritizedTest( clazz, pri );
192             tests.add( prioritizedTest );
193         }
194         sort( tests, new PrioritizedTestComparator() );
195         return tests;
196     }
197 
198     private List<Class<?>> transformToClasses( List<PrioritizedTest> tests )
199     {
200         List<Class<?>> result = new ArrayList<Class<?>>();
201         for ( PrioritizedTest test : tests )
202         {
203             result.add( test.getClazz() );
204         }
205         return result;
206     }
207 
208     private Map getPriorities( Comparator<Priority> priorityComparator )
209     {
210         Map<String, Priority> priorities = new HashMap<String, Priority>();
211         for ( Object o : runEntryStatistics.keySet() )
212         {
213             String testNames = (String) o;
214             String clazzName = extractClassName( testNames );
215             Priority priority = priorities.get( clazzName );
216             if ( priority == null )
217             {
218                 priority = new Priority( clazzName );
219                 priorities.put( clazzName, priority );
220             }
221 
222             RunEntryStatistics itemStat = runEntryStatistics.get( testNames );
223             priority.addItem( itemStat );
224         }
225 
226         List<Priority> items = new ArrayList<Priority>( priorities.values() );
227         sort( items, priorityComparator );
228         Map<String, Priority> result = new HashMap<String, Priority>();
229         int i = 0;
230         for ( Priority pri : items )
231         {
232             pri.setPriority( i++ );
233             result.put( pri.getClassName(), pri );
234         }
235         return result;
236     }
237 
238     static final class PrioritizedTestComparator
239         implements Comparator<PrioritizedTest>
240     {
241         public int compare( PrioritizedTest o, PrioritizedTest o1 )
242         {
243             return o.getPriority() - o1.getPriority();
244         }
245     }
246 
247     static final class TestRuntimeComparator
248         implements Comparator<Priority>
249     {
250         public int compare( Priority o, Priority o1 )
251         {
252             return o1.getTotalRuntime() - o.getTotalRuntime();
253         }
254     }
255 
256     static final class LeastFailureComparator
257         implements Comparator<Priority>
258     {
259         public int compare( Priority o, Priority o1 )
260         {
261             return o.getMinSuccessRate() - o1.getMinSuccessRate();
262         }
263     }
264 
265 
266     private static final Pattern PARENS = Pattern.compile( "^" + "[^\\(\\)]+" //non-parens
267                                                                + "\\((" // then an open-paren (start matching a group)
268                                                                + "[^\\\\(\\\\)]+" //non-parens
269                                                                + ")\\)" + "$" ); // then a close-paren (end group match)
270 
271     String extractClassName( String displayName )
272     {
273         Matcher m = PARENS.matcher( displayName );
274         return m.find() ? m.group( 1 ) : displayName;
275     }
276 }