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<>();
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 ( IOException e )
72              {
73                  throw new RuntimeException( e );
74              }
75          }
76          else
77          {
78              return new RunEntryStatisticsMap();
79          }
80      }
81  
82      static RunEntryStatisticsMap fromReader( Reader fileReader )
83          throws IOException
84      {
85          Map<String, RunEntryStatistics> result = new HashMap<>();
86          BufferedReader bufferedReader = new BufferedReader( fileReader );
87          String line = bufferedReader.readLine();
88          while ( line != null )
89          {
90              if ( !line.startsWith( "#" ) )
91              {
92                  final RunEntryStatistics stats = fromString( line );
93                  result.put( stats.getTestName(), stats );
94              }
95              line = bufferedReader.readLine();
96          }
97          return new RunEntryStatisticsMap( result );
98      }
99  
100     public void serialize( File file )
101         throws FileNotFoundException
102     {
103         FileOutputStream fos = new FileOutputStream( file );
104         try ( PrintWriter printWriter = new PrintWriter( fos ) )
105         {
106             List<RunEntryStatistics> items = new ArrayList<>( runEntryStatistics.values() );
107             sort( items, new RunCountComparator() );
108             for ( RunEntryStatistics item : items )
109             {
110                 printWriter.println( item.toString() );
111             }
112         }
113     }
114 
115     public RunEntryStatistics findOrCreate( ReportEntry reportEntry )
116     {
117         final RunEntryStatistics item = runEntryStatistics.get( reportEntry.getName() );
118         return item != null ? item : fromReportEntry( reportEntry );
119     }
120 
121     public RunEntryStatistics createNextGeneration( ReportEntry reportEntry )
122     {
123         final RunEntryStatistics newItem = findOrCreate( reportEntry );
124         final Integer elapsed = reportEntry.getElapsed();
125         return newItem.nextGeneration( elapsed != null ? elapsed : 0 );
126     }
127 
128     public RunEntryStatistics createNextGenerationFailure( ReportEntry reportEntry )
129     {
130         final RunEntryStatistics newItem = findOrCreate( reportEntry );
131         final Integer elapsed = reportEntry.getElapsed();
132         return newItem.nextGenerationFailure( elapsed != null ? elapsed : 0 );
133     }
134 
135     public void add( RunEntryStatistics item )
136     {
137         runEntryStatistics.put( item.getTestName(), item );
138     }
139 
140     static final class RunCountComparator
141         implements Comparator<RunEntryStatistics>
142     {
143         @Override
144         public int compare( RunEntryStatistics o, RunEntryStatistics o1 )
145         {
146             int runtime = o.getSuccessfulBuilds() - o1.getSuccessfulBuilds();
147             return runtime == 0 ? o.getRunTime() - o1.getRunTime() : runtime;
148         }
149     }
150 
151     public List<Class<?>> getPrioritizedTestsClassRunTime( List<Class<?>> testsToRun, int threadCount )
152     {
153         List<PrioritizedTest> prioritizedTests = getPrioritizedTests( testsToRun, new TestRuntimeComparator() );
154         ThreadedExecutionScheduler threadedExecutionScheduler = new ThreadedExecutionScheduler( threadCount );
155         for ( Object prioritizedTest1 : prioritizedTests )
156         {
157             threadedExecutionScheduler.addTest( (PrioritizedTest) prioritizedTest1 );
158         }
159 
160         return threadedExecutionScheduler.getResult();
161     }
162 
163     public List<Class<?>> getPrioritizedTestsByFailureFirst( List<Class<?>> testsToRun )
164     {
165         List<PrioritizedTest> prioritizedTests = getPrioritizedTests( testsToRun, new LeastFailureComparator() );
166         return transformToClasses( prioritizedTests );
167     }
168 
169     private List<PrioritizedTest> getPrioritizedTests( List<Class<?>> testsToRun,
170                                                        Comparator<Priority> priorityComparator )
171     {
172         Map classPriorities = getPriorities( priorityComparator );
173 
174         List<PrioritizedTest> tests = new ArrayList<>();
175         for ( Class<?> clazz : testsToRun )
176         {
177             Priority pri = (Priority) classPriorities.get( clazz.getName() );
178             if ( pri == null )
179             {
180                 pri = Priority.newTestClassPriority( clazz.getName() );
181             }
182             PrioritizedTest prioritizedTest = new PrioritizedTest( clazz, pri );
183             tests.add( prioritizedTest );
184         }
185         sort( tests, new PrioritizedTestComparator() );
186         return tests;
187     }
188 
189     private List<Class<?>> transformToClasses( List<PrioritizedTest> tests )
190     {
191         List<Class<?>> result = new ArrayList<>();
192         for ( PrioritizedTest test : tests )
193         {
194             result.add( test.getClazz() );
195         }
196         return result;
197     }
198 
199     private Map getPriorities( Comparator<Priority> priorityComparator )
200     {
201         Map<String, Priority> priorities = new HashMap<>();
202         for ( Object o : runEntryStatistics.keySet() )
203         {
204             String testNames = (String) o;
205             String clazzName = extractClassName( testNames );
206             Priority priority = priorities.get( clazzName );
207             if ( priority == null )
208             {
209                 priority = new Priority( clazzName );
210                 priorities.put( clazzName, priority );
211             }
212 
213             RunEntryStatistics itemStat = runEntryStatistics.get( testNames );
214             priority.addItem( itemStat );
215         }
216 
217         List<Priority> items = new ArrayList<>( priorities.values() );
218         sort( items, priorityComparator );
219         Map<String, Priority> result = new HashMap<>();
220         int i = 0;
221         for ( Priority pri : items )
222         {
223             pri.setPriority( i++ );
224             result.put( pri.getClassName(), pri );
225         }
226         return result;
227     }
228 
229     static final class PrioritizedTestComparator
230         implements Comparator<PrioritizedTest>
231     {
232         @Override
233         public int compare( PrioritizedTest o, PrioritizedTest o1 )
234         {
235             return o.getPriority() - o1.getPriority();
236         }
237     }
238 
239     static final class TestRuntimeComparator
240         implements Comparator<Priority>
241     {
242         @Override
243         public int compare( Priority o, Priority o1 )
244         {
245             return o1.getTotalRuntime() - o.getTotalRuntime();
246         }
247     }
248 
249     static final class LeastFailureComparator
250         implements Comparator<Priority>
251     {
252         @Override
253         public int compare( Priority o, Priority o1 )
254         {
255             return o.getMinSuccessRate() - o1.getMinSuccessRate();
256         }
257     }
258 
259 
260     private static final Pattern PARENS = Pattern.compile( "^" + "[^\\(\\)]+" //non-parens
261                                                                + "\\((" // then an open-paren (start matching a group)
262                                                                + "[^\\\\(\\\\)]+" //non-parens
263                                                                + ")\\)" + "$" ); // then a close-paren (end group match)
264 
265     String extractClassName( String displayName )
266     {
267         Matcher m = PARENS.matcher( displayName );
268         return m.find() ? m.group( 1 ) : displayName;
269     }
270 }