/* * Copyright 2010 Henry Coles * * Licensed 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. */ package org.pitest.mutationtest.tooling; import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Logger; import org.pitest.classinfo.ClassByteArraySource; import org.pitest.classinfo.ClassInfo; import org.pitest.classinfo.ClassName; import org.pitest.classinfo.HierarchicalClassId; import org.pitest.classpath.ClassPathByteArraySource; import org.pitest.classpath.CodeSource; import org.pitest.coverage.CoverageDatabase; import org.pitest.coverage.CoverageGenerator; import org.pitest.coverage.TestInfo; import org.pitest.functional.FCollection; import org.pitest.functional.prelude.Prelude; import org.pitest.help.Help; import org.pitest.help.PitHelpError; import org.pitest.mutationtest.HistoryStore; import org.pitest.mutationtest.ListenerArguments; import org.pitest.mutationtest.MutationAnalyser; import org.pitest.mutationtest.MutationConfig; import org.pitest.mutationtest.MutationResultListener; import org.pitest.mutationtest.build.MutationAnalysisUnit; import org.pitest.mutationtest.build.MutationGrouper; import org.pitest.mutationtest.build.MutationSource; import org.pitest.mutationtest.build.MutationTestBuilder; import org.pitest.mutationtest.build.PercentAndConstantTimeoutStrategy; import org.pitest.mutationtest.build.TestPrioritiser; import org.pitest.mutationtest.build.WorkerFactory; import org.pitest.mutationtest.config.ReportOptions; import org.pitest.mutationtest.config.SettingsFactory; import org.pitest.mutationtest.engine.MutationEngine; import org.pitest.mutationtest.execute.MutationAnalysisExecutor; import org.pitest.mutationtest.filter.MutationFilterFactory; import org.pitest.mutationtest.incremental.DefaultCodeHistory; import org.pitest.mutationtest.incremental.HistoryListener; import org.pitest.mutationtest.incremental.IncrementalAnalyser; import org.pitest.mutationtest.statistics.MutationStatisticsListener; import org.pitest.mutationtest.statistics.Score; import org.pitest.util.Log; import org.pitest.util.StringUtil; import org.pitest.util.Timings; public class MutationCoverage { private static final int MB = 1024 * 1024; private static final Logger LOG = Log.getLogger(); private final ReportOptions data; private final MutationStrategies strategies; private final Timings timings; private final CodeSource code; private final File baseDir; private final SettingsFactory settings; public MutationCoverage(final MutationStrategies strategies, final File baseDir, final CodeSource code, final ReportOptions data, final SettingsFactory settings, final Timings timings) { this.strategies = strategies; this.data = data; this.settings = settings; this.timings = timings; this.code = code; this.baseDir = baseDir; } public CombinedStatistics runReport() throws IOException { Log.setVerbose(this.data.isVerbose()); final Runtime runtime = Runtime.getRuntime(); if (!this.data.isVerbose()) { LOG.info("Verbose logging is disabled. If you encounter an problem please enable it before reporting an issue."); } LOG.fine("Running report with " + this.data); LOG.fine("System class path is " + System.getProperty("java.class.path")); LOG.fine("Maximum available memory is " + (runtime.maxMemory() / MB) + " mb"); final long t0 = System.currentTimeMillis(); verifyBuildSuitableForMutationTesting(); checkExcludedRunners(); final CoverageDatabase coverageData = coverage().calculateCoverage(); LOG.fine("Used memory after coverage calculation " + ((runtime.totalMemory() - runtime.freeMemory()) / MB) + " mb"); LOG.fine("Free Memory after coverage calculation " + (runtime.freeMemory() / MB) + " mb"); final MutationStatisticsListener stats = new MutationStatisticsListener(); final MutationEngine engine = this.strategies.factory().createEngine( this.data.isMutateStaticInitializers(), Prelude.or(this.data.getExcludedMethods()), this.data.getLoggingClasses(), this.data.getMutators(), this.data.isDetectInlinedCode()); final List<MutationResultListener> config = createConfig(t0, coverageData, stats, engine); history().initialize(); this.timings.registerStart(Timings.Stage.BUILD_MUTATION_TESTS); final List<MutationAnalysisUnit> tus = buildMutationTests(coverageData, engine); this.timings.registerEnd(Timings.Stage.BUILD_MUTATION_TESTS); LOG.info("Created " + tus.size() + " mutation test units"); checkMutationsFound(tus); recordClassPath(coverageData); LOG.fine("Used memory before analysis start " + ((runtime.totalMemory() - runtime.freeMemory()) / MB) + " mb"); LOG.fine("Free Memory before analysis start " + (runtime.freeMemory() / MB) + " mb"); final MutationAnalysisExecutor mae = new MutationAnalysisExecutor( numberOfThreads(), config); this.timings.registerStart(Timings.Stage.RUN_MUTATION_TESTS); mae.run(tus); this.timings.registerEnd(Timings.Stage.RUN_MUTATION_TESTS); LOG.info("Completed in " + timeSpan(t0)); printStats(stats); return new CombinedStatistics(stats.getStatistics(), coverageData.createSummary()); } private void checkExcludedRunners() { Collection<String> excludedRunners = this.data.getExcludedRunners(); if (!excludedRunners.isEmpty()) { // Check whether JUnit4 is available or not try { Class.forName("org.junit.runner.RunWith"); } catch (ClassNotFoundException e) { // JUnit4 is not available on the classpath throw new PitHelpError(Help.NO_JUNIT_EXCLUDE_RUNNERS); } } } private int numberOfThreads() { return Math.max(1, this.data.getNumberOfThreads()); } private List<MutationResultListener> createConfig(final long t0, final CoverageDatabase coverageData, final MutationStatisticsListener stats, final MutationEngine engine) { final List<MutationResultListener> ls = new ArrayList<MutationResultListener>(); ls.add(stats); final ListenerArguments args = new ListenerArguments( this.strategies.output(), coverageData, new SmartSourceLocator( this.data.getSourceDirs()), engine, t0); final MutationResultListener mutationReportListener = this.strategies .listenerFactory().getListener(this.data.getFreeFormProperties(), args); ls.add(mutationReportListener); ls.add(new HistoryListener(history())); if (!this.data.isVerbose()) { ls.add(new SpinnerListener(System.out)); } return ls; } private void recordClassPath(final CoverageDatabase coverageData) { final Set<ClassName> allClassNames = getAllClassesAndTests(coverageData); final Collection<HierarchicalClassId> ids = FCollection.map( this.code.getClassInfo(allClassNames), ClassInfo.toFullClassId()); history().recordClassPath(ids, coverageData); } private Set<ClassName> getAllClassesAndTests( final CoverageDatabase coverageData) { final Set<ClassName> names = new HashSet<ClassName>(); for (final ClassName each : this.code.getCodeUnderTestNames()) { names.add(each); FCollection.mapTo(coverageData.getTestsForClass(each), TestInfo.toDefiningClassName(), names); } return names; } private void verifyBuildSuitableForMutationTesting() { this.strategies.buildVerifier().verify(this.code); } private void printStats(final MutationStatisticsListener stats) { final PrintStream ps = System.out; ps.println(StringUtil.separatorLine('=')); ps.println("- Timings"); ps.println(StringUtil.separatorLine('=')); this.timings.report(ps); ps.println(StringUtil.separatorLine('=')); ps.println("- Statistics"); ps.println(StringUtil.separatorLine('=')); stats.getStatistics().report(ps); ps.println(StringUtil.separatorLine('=')); ps.println("- Mutators"); ps.println(StringUtil.separatorLine('=')); for (final Score each : stats.getStatistics().getScores()) { each.report(ps); ps.println(StringUtil.separatorLine()); } } private List<MutationAnalysisUnit> buildMutationTests( final CoverageDatabase coverageData, final MutationEngine engine) { final MutationConfig mutationConfig = new MutationConfig(engine, coverage() .getLaunchOptions()); ClassByteArraySource bas = new ClassPathByteArraySource( this.data.getClassPath()); TestPrioritiser testPrioritiser = this.settings.getTestPrioritiser() .makeTestPrioritiser(this.data.getFreeFormProperties(), this.code, coverageData); final MutationSource source = new MutationSource(mutationConfig, makeFilter().createFilter(this.data.getFreeFormProperties(), this.code, this.data.getMaxMutationsPerClass()), testPrioritiser, bas); final MutationAnalyser analyser = new IncrementalAnalyser( new DefaultCodeHistory(this.code, history()), coverageData); final WorkerFactory wf = new WorkerFactory(this.baseDir, coverage() .getConfiguration(), mutationConfig, new PercentAndConstantTimeoutStrategy(this.data.getTimeoutFactor(), this.data.getTimeoutConstant()), this.data.isVerbose(), this.data .getClassPath().getLocalClassPath()); MutationGrouper grouper = this.settings.getMutationGrouper().makeFactory( this.data.getFreeFormProperties(), this.code, this.data.getNumberOfThreads(), this.data.getMutationUnitSize()); final MutationTestBuilder builder = new MutationTestBuilder(wf, analyser, source, grouper); return builder.createMutationTestUnits(this.code.getCodeUnderTestNames()); } private MutationFilterFactory makeFilter() { return this.settings.createMutationFilter(); } private void checkMutationsFound(final List<MutationAnalysisUnit> tus) { if (tus.isEmpty()) { if (this.data.shouldFailWhenNoMutations()) { throw new PitHelpError(Help.NO_MUTATIONS_FOUND); } else { LOG.warning(Help.NO_MUTATIONS_FOUND.toString()); } } } private String timeSpan(final long t0) { return "" + ((System.currentTimeMillis() - t0) / 1000) + " seconds"; } private CoverageGenerator coverage() { return this.strategies.coverage(); } private HistoryStore history() { return this.strategies.history(); } }