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