/* * 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.execute; import static org.pitest.util.Unchecked.translateCheckedException; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import org.pitest.classinfo.ClassName; import org.pitest.classpath.ClassPath; import org.pitest.functional.F3; import org.pitest.mutationtest.DetectionStatus; import org.pitest.mutationtest.MutationStatusTestPair; import org.pitest.mutationtest.engine.Mutant; import org.pitest.mutationtest.engine.Mutater; import org.pitest.mutationtest.engine.MutationDetails; import org.pitest.mutationtest.engine.MutationIdentifier; import org.pitest.mutationtest.mocksupport.JavassistInterceptor; import org.pitest.testapi.TestResult; import org.pitest.testapi.TestUnit; import org.pitest.testapi.execute.Container; import org.pitest.testapi.execute.ExitingResultCollector; import org.pitest.testapi.execute.MultipleTestGroup; import org.pitest.testapi.execute.Pitest; import org.pitest.testapi.execute.containers.ConcreteResultCollector; import org.pitest.testapi.execute.containers.UnContainer; import org.pitest.util.IsolationUtils; import org.pitest.util.Log; public class MutationTestWorker { private static final Logger LOG = Log .getLogger(); // micro optimise debug logging private static final boolean DEBUG = LOG .isLoggable(Level.FINE); private final Mutater mutater; private final ClassLoader loader; private final F3<ClassName, ClassLoader, byte[], Boolean> hotswap; public MutationTestWorker( final F3<ClassName, ClassLoader, byte[], Boolean> hotswap, final Mutater mutater, final ClassLoader loader) { this.loader = loader; this.mutater = mutater; this.hotswap = hotswap; } protected void run(final Collection<MutationDetails> range, final Reporter r, final TimeOutDecoratedTestSource testSource) throws IOException { for (final MutationDetails mutation : range) { if (DEBUG) { LOG.fine("Running mutation " + mutation); } final long t0 = System.currentTimeMillis(); processMutation(r, testSource, mutation); if (DEBUG) { LOG.fine("processed mutation in " + (System.currentTimeMillis() - t0) + " ms."); } } } private void processMutation(final Reporter r, final TimeOutDecoratedTestSource testSource, final MutationDetails mutationDetails) throws IOException { final MutationIdentifier mutationId = mutationDetails.getId(); final Mutant mutatedClass = this.mutater.getMutation(mutationId); // For the benefit of mocking frameworks such as PowerMock // mess with the internals of Javassist so our mutated class // bytes are returned JavassistInterceptor.setMutant(mutatedClass); if (DEBUG) { LOG.fine("mutating method " + mutatedClass.getDetails().getMethod()); } final List<TestUnit> relevantTests = testSource .translateTests(mutationDetails.getTestsInOrder()); r.describe(mutationId); final MutationStatusTestPair mutationDetected = handleMutation( mutationDetails, mutatedClass, relevantTests); r.report(mutationId, mutationDetected); if (DEBUG) { LOG.fine("Mutation " + mutationId + " detected = " + mutationDetected); } } private MutationStatusTestPair handleMutation( final MutationDetails mutationId, final Mutant mutatedClass, final List<TestUnit> relevantTests) { MutationStatusTestPair mutationDetected; if ((relevantTests == null) || relevantTests.isEmpty()) { LOG.info("No test coverage for mutation " + mutationId + " in " + mutatedClass.getDetails().getMethod()); mutationDetected = new MutationStatusTestPair(0, DetectionStatus.RUN_ERROR); } else { mutationDetected = handleCoveredMutation(mutationId, mutatedClass, relevantTests); } return mutationDetected; } private MutationStatusTestPair handleCoveredMutation( final MutationDetails mutationId, final Mutant mutatedClass, final List<TestUnit> relevantTests) { MutationStatusTestPair mutationDetected; if (DEBUG) { LOG.fine("" + relevantTests.size() + " relevant test for " + mutatedClass.getDetails().getMethod()); } final ClassLoader activeloader = pickClassLoaderForMutant(mutationId); final Container c = createNewContainer(activeloader); final long t0 = System.currentTimeMillis(); if (this.hotswap.apply(mutationId.getClassName(), activeloader, mutatedClass.getBytes())) { if (DEBUG) { LOG.fine("replaced class with mutant in " + (System.currentTimeMillis() - t0) + " ms"); } mutationDetected = doTestsDetectMutation(c, relevantTests); } else { LOG.warning("Mutation " + mutationId + " was not viable "); mutationDetected = new MutationStatusTestPair(0, DetectionStatus.NON_VIABLE); } return mutationDetected; } private static Container createNewContainer(final ClassLoader activeloader) { final Container c = new UnContainer() { @Override public List<TestResult> execute(final TestUnit group) { List<TestResult> results = new ArrayList<TestResult>(); final ExitingResultCollector rc = new ExitingResultCollector( new ConcreteResultCollector(results)); group.execute(activeloader, rc); return results; } }; return c; } private ClassLoader pickClassLoaderForMutant(final MutationDetails mutant) { if (mutant.mayPoisonJVM()) { if (DEBUG) { LOG.fine("Creating new classloader for static initializer"); } return new DefaultPITClassloader(new ClassPath(), IsolationUtils.bootClassLoader()); } else { return this.loader; } } @Override public String toString() { return "MutationTestWorker [mutater=" + this.mutater + ", loader=" + this.loader + ", hotswap=" + this.hotswap + "]"; } private MutationStatusTestPair doTestsDetectMutation(final Container c, final List<TestUnit> tests) { try { final CheckTestHasFailedResultListener listener = new CheckTestHasFailedResultListener(); final Pitest pit = new Pitest(Collections.singletonList(listener)); pit.run(c, createEarlyExitTestGroup(tests)); return createStatusTestPair(listener); } catch (final Exception ex) { throw translateCheckedException(ex); } } private MutationStatusTestPair createStatusTestPair( final CheckTestHasFailedResultListener listener) { if (listener.lastFailingTest().hasSome()) { return new MutationStatusTestPair(listener.getNumberOfTestsRun(), listener.status(), listener.lastFailingTest().value() .getQualifiedName()); } else { return new MutationStatusTestPair(listener.getNumberOfTestsRun(), listener.status()); } } private List<TestUnit> createEarlyExitTestGroup(final List<TestUnit> tests) { return Collections.<TestUnit> singletonList(new MultipleTestGroup(tests)); } }