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  import org.apache.maven.surefire.report.ReportEntry;
23  import org.apache.maven.surefire.util.internal.ClassMethod;
24  
25  import java.io.BufferedWriter;
26  import java.io.File;
27  import java.io.FileInputStream;
28  import java.io.FileOutputStream;
29  import java.io.IOException;
30  import java.io.InputStream;
31  import java.io.OutputStream;
32  import java.io.OutputStreamWriter;
33  import java.util.ArrayList;
34  import java.util.Comparator;
35  import java.util.HashMap;
36  import java.util.Iterator;
37  import java.util.List;
38  import java.util.Map;
39  import java.util.Map.Entry;
40  import java.util.Scanner;
41  import java.util.StringTokenizer;
42  import java.util.concurrent.ConcurrentHashMap;
43  
44  import static java.lang.Integer.parseInt;
45  import static java.nio.charset.StandardCharsets.UTF_8;
46  import static java.util.Collections.sort;
47  import static org.apache.maven.surefire.util.internal.StringUtils.NL;
48  
49  /**
50   * @author Kristian Rosenvold
51   */
52  public final class RunEntryStatisticsMap
53  {
54      private final Map<ClassMethod, RunEntryStatistics> runEntryStatistics;
55  
56      private RunEntryStatisticsMap( Map<ClassMethod, RunEntryStatistics> runEntryStatistics )
57      {
58          this.runEntryStatistics = new ConcurrentHashMap<>( runEntryStatistics );
59      }
60  
61      public RunEntryStatisticsMap()
62      {
63          runEntryStatistics = new ConcurrentHashMap<>();
64      }
65  
66      public static RunEntryStatisticsMap fromFile( File file )
67      {
68          if ( file.exists() )
69          {
70              try
71              {
72                  return fromStream( new FileInputStream( file ) );
73              }
74              catch ( IOException e )
75              {
76                  throw new RuntimeException( e );
77              }
78          }
79          else
80          {
81              return new RunEntryStatisticsMap();
82          }
83      }
84  
85      static RunEntryStatisticsMap fromStream( InputStream fileReader )
86      {
87          Map<ClassMethod, RunEntryStatistics> result = new HashMap<>();
88          try ( Scanner scanner = new Scanner( fileReader, "UTF-8" ) )
89          {
90              RunEntryStatistics previous = null;
91              while ( scanner.hasNextLine() )
92              {
93                  String line = scanner.nextLine();
94  
95                  if ( line.charAt( 0 ) == ' ' )
96                  {
97                      previous = new RunEntryStatistics( previous.getRunTime(),
98                              previous.getSuccessfulBuilds(),
99                              previous.getClassMethod().getClazz(),
100                             previous.getClassMethod().getMethod() + NL + line.substring( 1 ) );
101                 }
102                 else
103                 {
104                     if ( previous != null )
105                     {
106                         result.put( previous.getClassMethod(), previous );
107                     }
108                     StringTokenizer tokenizer = new StringTokenizer( line, "," );
109 
110                     int methodIndex = 3;
111 
112                     String successfulBuildsString = tokenizer.nextToken();
113                     int successfulBuilds = parseInt( successfulBuildsString );
114 
115                     methodIndex += successfulBuildsString.length();
116 
117                     String runTimeString = tokenizer.nextToken();
118                     int runTime = parseInt( runTimeString );
119 
120                     methodIndex += runTimeString.length();
121 
122                     String className = tokenizer.nextToken();
123 
124                     methodIndex += className.length();
125 
126                     String methodName = line.substring( methodIndex );
127 
128                     ClassMethod classMethod = new ClassMethod( className, methodName );
129                     previous = new RunEntryStatistics( runTime, successfulBuilds, classMethod );
130                 }
131             }
132             if ( previous != null )
133             {
134                 result.put( previous.getClassMethod(), previous );
135             }
136         }
137         return new RunEntryStatisticsMap( result );
138     }
139 
140     public void serialize( File statsFile )
141         throws IOException
142     {
143         if ( statsFile.isFile() )
144         {
145             //noinspection ResultOfMethodCallIgnored
146             statsFile.delete();
147         }
148         OutputStream os = new FileOutputStream( statsFile );
149         try ( BufferedWriter writer = new BufferedWriter( new OutputStreamWriter( os, UTF_8 ), 64 * 1024 ) )
150         {
151             List<RunEntryStatistics> items = new ArrayList<>( runEntryStatistics.values() );
152             sort( items, new RunCountComparator() );
153             for ( Iterator<RunEntryStatistics> it = items.iterator(); it.hasNext(); )
154             {
155                 RunEntryStatistics item = it.next();
156                 ClassMethod test = item.getClassMethod();
157                 String line = item.getSuccessfulBuilds() + "," + item.getRunTime() + "," + test.getClazz() + ",";
158                 writer.write( line );
159                 boolean wasFirstLine = false;
160                 for ( Scanner scanner = new Scanner( test.getMethod() ); scanner.hasNextLine(); wasFirstLine = true )
161                 {
162                     String methodLine = scanner.nextLine();
163                     if ( wasFirstLine )
164                     {
165                         writer.write( ' ' );
166                     }
167                     writer.write( methodLine );
168                     if ( scanner.hasNextLine() )
169                     {
170                         writer.newLine();
171                     }
172                 }
173                 if ( it.hasNext() )
174                 {
175                     writer.newLine();
176                 }
177             }
178         }
179     }
180 
181     private RunEntryStatistics findOrCreate( ReportEntry reportEntry )
182     {
183         ClassMethod classMethod = new ClassMethod( reportEntry.getSourceName(), reportEntry.getName() );
184         RunEntryStatistics item = runEntryStatistics.get( classMethod );
185         return item != null ? item : new RunEntryStatistics( reportEntry.getElapsed( 0 ), 0, classMethod );
186     }
187 
188     public RunEntryStatistics createNextGeneration( ReportEntry reportEntry )
189     {
190         RunEntryStatistics newItem = findOrCreate( reportEntry );
191         return newItem.nextGeneration( reportEntry.getElapsed( 0 ) );
192     }
193 
194     public RunEntryStatistics createNextGenerationFailure( ReportEntry reportEntry )
195     {
196         RunEntryStatistics newItem = findOrCreate( reportEntry );
197         return newItem.nextGenerationFailure( reportEntry.getElapsed( 0 ) );
198     }
199 
200     public void add( RunEntryStatistics item )
201     {
202         runEntryStatistics.put( item.getClassMethod(), item );
203     }
204 
205     static final class RunCountComparator
206         implements Comparator<RunEntryStatistics>
207     {
208         @Override
209         public int compare( RunEntryStatistics o, RunEntryStatistics o1 )
210         {
211             int runtime = o.getSuccessfulBuilds() - o1.getSuccessfulBuilds();
212             return runtime == 0 ? o.getRunTime() - o1.getRunTime() : runtime;
213         }
214     }
215 
216     public List<Class<?>> getPrioritizedTestsClassRunTime( List<Class<?>> testsToRun, int threadCount )
217     {
218         List<PrioritizedTest> prioritizedTests = getPrioritizedTests( testsToRun, new TestRuntimeComparator() );
219         ThreadedExecutionScheduler threadedExecutionScheduler = new ThreadedExecutionScheduler( threadCount );
220         for ( Object prioritizedTest1 : prioritizedTests )
221         {
222             threadedExecutionScheduler.addTest( (PrioritizedTest) prioritizedTest1 );
223         }
224 
225         return threadedExecutionScheduler.getResult();
226     }
227 
228     public List<Class<?>> getPrioritizedTestsByFailureFirst( List<Class<?>> testsToRun )
229     {
230         List<PrioritizedTest> prioritizedTests = getPrioritizedTests( testsToRun, new LeastFailureComparator() );
231         return transformToClasses( prioritizedTests );
232     }
233 
234     private List<PrioritizedTest> getPrioritizedTests( List<Class<?>> testsToRun,
235                                                        Comparator<Priority> priorityComparator )
236     {
237         Map<String, Priority> classPriorities = getPriorities( priorityComparator );
238 
239         List<PrioritizedTest> tests = new ArrayList<>();
240         for ( Class<?> clazz : testsToRun )
241         {
242             Priority pri = classPriorities.get( clazz.getName() );
243             if ( pri == null )
244             {
245                 pri = Priority.newTestClassPriority( clazz.getName() );
246             }
247             PrioritizedTest prioritizedTest = new PrioritizedTest( clazz, pri );
248             tests.add( prioritizedTest );
249         }
250         sort( tests, new PrioritizedTestComparator() );
251         return tests;
252     }
253 
254     private static List<Class<?>> transformToClasses( List<PrioritizedTest> tests )
255     {
256         List<Class<?>> result = new ArrayList<>();
257         for ( PrioritizedTest test : tests )
258         {
259             result.add( test.getClazz() );
260         }
261         return result;
262     }
263 
264     private Map<String, Priority> getPriorities( Comparator<Priority> priorityComparator )
265     {
266         Map<String, Priority> priorities = new HashMap<>();
267         for ( Entry<ClassMethod, RunEntryStatistics> testNames : runEntryStatistics.entrySet() )
268         {
269             String clazzName = testNames.getKey().getClazz();
270             Priority priority = priorities.get( clazzName );
271             if ( priority == null )
272             {
273                 priority = new Priority( clazzName );
274                 priorities.put( clazzName, priority );
275             }
276             priority.addItem( testNames.getValue() );
277         }
278 
279         List<Priority> items = new ArrayList<>( priorities.values() );
280         sort( items, priorityComparator );
281         Map<String, Priority> result = new HashMap<>();
282         int i = 0;
283         for ( Priority pri : items )
284         {
285             pri.setPriority( i++ );
286             result.put( pri.getClassName(), pri );
287         }
288         return result;
289     }
290 
291     static final class PrioritizedTestComparator
292         implements Comparator<PrioritizedTest>
293     {
294         @Override
295         public int compare( PrioritizedTest o, PrioritizedTest o1 )
296         {
297             return o.getPriority() - o1.getPriority();
298         }
299     }
300 
301     static final class TestRuntimeComparator
302         implements Comparator<Priority>
303     {
304         @Override
305         public int compare( Priority o, Priority o1 )
306         {
307             return o1.getTotalRuntime() - o.getTotalRuntime();
308         }
309     }
310 
311     static final class LeastFailureComparator
312         implements Comparator<Priority>
313     {
314         @Override
315         public int compare( Priority o, Priority o1 )
316         {
317             return o.getMinSuccessRate() - o1.getMinSuccessRate();
318         }
319     }
320 }