blob: 8bf02686c4aebcb933823816055f519ee8a95fcf [file] [log] [blame]
package org.apache.maven.surefire.api.runorder;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import org.apache.maven.surefire.api.report.ReportEntry;
import org.apache.maven.surefire.api.util.internal.ClassMethod;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Scanner;
import java.util.concurrent.ConcurrentHashMap;
import static java.lang.Integer.parseInt;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.sort;
import static org.apache.maven.surefire.api.util.internal.StringUtils.NL;
/**
* @author Kristian Rosenvold
*/
public final class RunEntryStatisticsMap
{
private final Map<ClassMethod, RunEntryStatistics> runEntryStatistics;
private RunEntryStatisticsMap( Map<ClassMethod, RunEntryStatistics> runEntryStatistics )
{
this.runEntryStatistics = new ConcurrentHashMap<>( runEntryStatistics );
}
public RunEntryStatisticsMap()
{
runEntryStatistics = new ConcurrentHashMap<>();
}
public static RunEntryStatisticsMap fromFile( File file )
{
if ( file.exists() )
{
try
{
return fromStream( new FileInputStream( file ) );
}
catch ( IOException e )
{
throw new RuntimeException( e );
}
}
else
{
return new RunEntryStatisticsMap();
}
}
public static RunEntryStatisticsMap fromStream( InputStream fileReader )
{
Map<ClassMethod, RunEntryStatistics> result = new HashMap<>();
try ( Scanner scanner = new Scanner( fileReader, "UTF-8" ) )
{
RunEntryStatistics previous = null;
while ( scanner.hasNextLine() )
{
String line = scanner.nextLine();
if ( line.charAt( 0 ) == ' ' )
{
previous = new RunEntryStatistics( previous.getRunTime(),
previous.getSuccessfulBuilds(),
previous.getClassMethod().getClazz(),
previous.getClassMethod().getMethod() + NL + line.substring( 1 ) );
}
else
{
if ( previous != null )
{
result.put( previous.getClassMethod(), previous );
}
int to = line.indexOf( ',' );
String successfulBuildsString = line.substring( 0, to );
int successfulBuilds = parseInt( successfulBuildsString );
int from = 1 + to;
to = line.indexOf( ',', from + 1 );
String runTimeString = line.substring( from, to );
int runTime = parseInt( runTimeString );
from = 1 + to;
to = line.indexOf( ',', from + 1 );
String className = to == -1 ? line.substring( from ) : line.substring( from, to );
from = 1 + to;
String methodName = to == -1 ? null : line.substring( from );
ClassMethod classMethod = new ClassMethod( className, methodName );
previous = new RunEntryStatistics( runTime, successfulBuilds, classMethod );
}
}
if ( previous != null )
{
result.put( previous.getClassMethod(), previous );
}
}
return new RunEntryStatisticsMap( result );
}
public void serialize( File statsFile )
throws IOException
{
if ( statsFile.isFile() )
{
//noinspection ResultOfMethodCallIgnored
statsFile.delete();
}
OutputStream os = new FileOutputStream( statsFile );
try ( BufferedWriter writer = new BufferedWriter( new OutputStreamWriter( os, UTF_8 ), 64 * 1024 ) )
{
List<RunEntryStatistics> items = new ArrayList<>( runEntryStatistics.values() );
sort( items, new RunCountComparator() );
for ( Iterator<RunEntryStatistics> it = items.iterator(); it.hasNext(); )
{
RunEntryStatistics item = it.next();
ClassMethod test = item.getClassMethod();
String line = item.getSuccessfulBuilds() + "," + item.getRunTime() + "," + test.getClazz() + ",";
writer.write( line );
boolean wasFirstLine = false;
String method = test.getMethod();
if ( method == null )
{
continue;
}
for ( Scanner scanner = new Scanner( method ); scanner.hasNextLine(); wasFirstLine = true )
{
String methodLine = scanner.nextLine();
if ( wasFirstLine )
{
writer.write( ' ' );
}
writer.write( methodLine );
if ( scanner.hasNextLine() )
{
writer.newLine();
}
}
if ( it.hasNext() )
{
writer.newLine();
}
}
}
}
private RunEntryStatistics findOrCreate( ReportEntry reportEntry )
{
ClassMethod classMethod = new ClassMethod( reportEntry.getSourceName(), reportEntry.getName() );
RunEntryStatistics item = runEntryStatistics.get( classMethod );
return item != null ? item : new RunEntryStatistics( reportEntry.getElapsed( 0 ), 0, classMethod );
}
public RunEntryStatistics createNextGeneration( ReportEntry reportEntry )
{
RunEntryStatistics newItem = findOrCreate( reportEntry );
return newItem.nextGeneration( reportEntry.getElapsed( 0 ) );
}
public RunEntryStatistics createNextGenerationFailure( ReportEntry reportEntry )
{
RunEntryStatistics newItem = findOrCreate( reportEntry );
return newItem.nextGenerationFailure( reportEntry.getElapsed( 0 ) );
}
public void add( RunEntryStatistics item )
{
runEntryStatistics.put( item.getClassMethod(), item );
}
static final class RunCountComparator
implements Comparator<RunEntryStatistics>
{
@Override
public int compare( RunEntryStatistics o, RunEntryStatistics o1 )
{
int runtime = o.getSuccessfulBuilds() - o1.getSuccessfulBuilds();
return runtime == 0 ? o.getRunTime() - o1.getRunTime() : runtime;
}
}
public List<Class<?>> getPrioritizedTestsClassRunTime( List<Class<?>> testsToRun, int threadCount )
{
List<PrioritizedTest> prioritizedTests = getPrioritizedTests( testsToRun, new TestRuntimeComparator() );
ThreadedExecutionScheduler threadedExecutionScheduler = new ThreadedExecutionScheduler( threadCount );
for ( Object prioritizedTest1 : prioritizedTests )
{
threadedExecutionScheduler.addTest( (PrioritizedTest) prioritizedTest1 );
}
return threadedExecutionScheduler.getResult();
}
public List<Class<?>> getPrioritizedTestsByFailureFirst( List<Class<?>> testsToRun )
{
List<PrioritizedTest> prioritizedTests = getPrioritizedTests( testsToRun, new LeastFailureComparator() );
return transformToClasses( prioritizedTests );
}
private List<PrioritizedTest> getPrioritizedTests( List<Class<?>> testsToRun,
Comparator<Priority> priorityComparator )
{
Map<String, Priority> classPriorities = getPriorities( priorityComparator );
List<PrioritizedTest> tests = new ArrayList<>();
for ( Class<?> clazz : testsToRun )
{
Priority pri = classPriorities.get( clazz.getName() );
if ( pri == null )
{
pri = Priority.newTestClassPriority( clazz.getName() );
}
PrioritizedTest prioritizedTest = new PrioritizedTest( clazz, pri );
tests.add( prioritizedTest );
}
sort( tests, new PrioritizedTestComparator() );
return tests;
}
private static List<Class<?>> transformToClasses( List<PrioritizedTest> tests )
{
List<Class<?>> result = new ArrayList<>();
for ( PrioritizedTest test : tests )
{
result.add( test.getClazz() );
}
return result;
}
private Map<String, Priority> getPriorities( Comparator<Priority> priorityComparator )
{
Map<String, Priority> priorities = new HashMap<>();
for ( Entry<ClassMethod, RunEntryStatistics> testNames : runEntryStatistics.entrySet() )
{
String clazzName = testNames.getKey().getClazz();
Priority priority = priorities.get( clazzName );
if ( priority == null )
{
priority = new Priority( clazzName );
priorities.put( clazzName, priority );
}
priority.addItem( testNames.getValue() );
}
List<Priority> items = new ArrayList<>( priorities.values() );
sort( items, priorityComparator );
Map<String, Priority> result = new HashMap<>();
int i = 0;
for ( Priority pri : items )
{
pri.setPriority( i++ );
result.put( pri.getClassName(), pri );
}
return result;
}
static final class PrioritizedTestComparator
implements Comparator<PrioritizedTest>
{
@Override
public int compare( PrioritizedTest o, PrioritizedTest o1 )
{
return o.getPriority() - o1.getPriority();
}
}
static final class TestRuntimeComparator
implements Comparator<Priority>
{
@Override
public int compare( Priority o, Priority o1 )
{
return o1.getTotalRuntime() - o.getTotalRuntime();
}
}
static final class LeastFailureComparator
implements Comparator<Priority>
{
@Override
public int compare( Priority o, Priority o1 )
{
return o.getMinSuccessRate() - o1.getMinSuccessRate();
}
}
}