package org.apache.maven.plugin.surefire.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.report.ReportEntry; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; import java.io.Reader; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import static java.util.Collections.sort; import static org.apache.maven.plugin.surefire.runorder.RunEntryStatistics.fromReportEntry; import static org.apache.maven.plugin.surefire.runorder.RunEntryStatistics.fromString; /** * @author Kristian Rosenvold */ public final class RunEntryStatisticsMap { private final Map<String, RunEntryStatistics> runEntryStatistics; public RunEntryStatisticsMap( Map<String, RunEntryStatistics> runEntryStatistics ) { this.runEntryStatistics = new ConcurrentHashMap<String, RunEntryStatistics>( runEntryStatistics ); } public RunEntryStatisticsMap() { runEntryStatistics = new ConcurrentHashMap<String, RunEntryStatistics>(); } public static RunEntryStatisticsMap fromFile( File file ) { if ( file.exists() ) { try { return fromReader( new FileReader( file ) ); } catch ( FileNotFoundException e ) { throw new RuntimeException( e ); } catch ( IOException e ) { throw new RuntimeException( e ); } } else { return new RunEntryStatisticsMap(); } } static RunEntryStatisticsMap fromReader( Reader fileReader ) throws IOException { Map<String, RunEntryStatistics> result = new HashMap<String, RunEntryStatistics>(); BufferedReader bufferedReader = new BufferedReader( fileReader ); String line = bufferedReader.readLine(); while ( line != null ) { if ( !line.startsWith( "#" ) ) { final RunEntryStatistics stats = fromString( line ); result.put( stats.getTestName(), stats ); } line = bufferedReader.readLine(); } return new RunEntryStatisticsMap( result ); } public void serialize( File file ) throws FileNotFoundException { FileOutputStream fos = new FileOutputStream( file ); PrintWriter printWriter = new PrintWriter( fos ); try { List<RunEntryStatistics> items = new ArrayList<RunEntryStatistics>( runEntryStatistics.values() ); sort( items, new RunCountComparator() ); for ( RunEntryStatistics item : items ) { printWriter.println( item.toString() ); } printWriter.flush(); } finally { printWriter.close(); } } public RunEntryStatistics findOrCreate( ReportEntry reportEntry ) { final RunEntryStatistics item = runEntryStatistics.get( reportEntry.getName() ); return item != null ? item : fromReportEntry( reportEntry ); } public RunEntryStatistics createNextGeneration( ReportEntry reportEntry ) { final RunEntryStatistics newItem = findOrCreate( reportEntry ); final Integer elapsed = reportEntry.getElapsed(); return newItem.nextGeneration( elapsed != null ? elapsed : 0 ); } public RunEntryStatistics createNextGenerationFailure( ReportEntry reportEntry ) { final RunEntryStatistics newItem = findOrCreate( reportEntry ); final Integer elapsed = reportEntry.getElapsed(); return newItem.nextGenerationFailure( elapsed != null ? elapsed : 0 ); } public void add( RunEntryStatistics item ) { runEntryStatistics.put( item.getTestName(), 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 classPriorities = getPriorities( priorityComparator ); List<PrioritizedTest> tests = new ArrayList<PrioritizedTest>(); for ( Class<?> clazz : testsToRun ) { Priority pri = (Priority) 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 List<Class<?>> transformToClasses( List<PrioritizedTest> tests ) { List<Class<?>> result = new ArrayList<Class<?>>(); for ( PrioritizedTest test : tests ) { result.add( test.getClazz() ); } return result; } private Map getPriorities( Comparator<Priority> priorityComparator ) { Map<String, Priority> priorities = new HashMap<String, Priority>(); for ( Object o : runEntryStatistics.keySet() ) { String testNames = (String) o; String clazzName = extractClassName( testNames ); Priority priority = priorities.get( clazzName ); if ( priority == null ) { priority = new Priority( clazzName ); priorities.put( clazzName, priority ); } RunEntryStatistics itemStat = runEntryStatistics.get( testNames ); priority.addItem( itemStat ); } List<Priority> items = new ArrayList<Priority>( priorities.values() ); sort( items, priorityComparator ); Map<String, Priority> result = new HashMap<String, Priority>(); 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(); } } private static final Pattern PARENS = Pattern.compile( "^" + "[^\\(\\)]+" //non-parens + "\\((" // then an open-paren (start matching a group) + "[^\\\\(\\\\)]+" //non-parens + ")\\)" + "$" ); // then a close-paren (end group match) String extractClassName( String displayName ) { Matcher m = PARENS.matcher( displayName ); return m.find() ? m.group( 1 ) : displayName; } }