/* * Copyright 2011 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.testng; import java.util.Collections; import java.util.List; import java.util.concurrent.Callable; import org.pitest.testapi.AbstractTestUnit; import org.pitest.testapi.ResultCollector; import org.pitest.testapi.TestGroupConfig; import org.pitest.testapi.foreignclassloader.Events; import org.pitest.util.ClassLoaderDetectionStrategy; import org.pitest.util.IsolationUtils; import org.pitest.util.Unchecked; import org.testng.IInvokedMethod; import org.testng.IInvokedMethodListener; import org.testng.ITestContext; import org.testng.ITestListener; import org.testng.ITestResult; import org.testng.SkipException; import org.testng.TestNG; import org.testng.xml.XmlClass; import org.testng.xml.XmlSuite; import org.testng.xml.XmlTest; /** * Runs tests within a class via TestNG. It would be possible to create a test * unit per method using TestNG's filters but this is about ten times slower and * probably more than negates any advantage from more finely targeting the * tests. */ public class TestNGTestUnit extends AbstractTestUnit { // needs to be static as jmockit assumes only a single instance per jvm private static final TestNG TESTNG = new TestNG(false); private static final MutableTestListenerWrapper LISTENER = new MutableTestListenerWrapper(); static { TESTNG.addListener(LISTENER); TESTNG.addInvokedMethodListener(new FailFast(LISTENER)); } private final ClassLoaderDetectionStrategy classloaderDetection; private final Class<?> clazz; private final TestGroupConfig config; public TestNGTestUnit( final ClassLoaderDetectionStrategy classloaderDetection, final Class<?> clazz, final TestGroupConfig config) { super(new org.pitest.testapi.Description("_", clazz)); this.clazz = clazz; this.classloaderDetection = classloaderDetection; this.config = config; } public TestNGTestUnit(final Class<?> clazz, final TestGroupConfig config) { this(IsolationUtils.loaderDetectionStrategy(), clazz, config); } @Override public void execute(final ClassLoader loader, final ResultCollector rc) { synchronized (TESTNG) { if (this.classloaderDetection.fromDifferentLoader(this.clazz, loader)) { executeInForeignLoader(rc, loader); } else { executeInCurrentLoader(rc); } } } private void executeInForeignLoader(ResultCollector rc, ClassLoader loader) { @SuppressWarnings("unchecked") Callable<List<String>> e = (Callable<List<String>>) IsolationUtils .cloneForLoader(new ForeignClassLoaderTestNGExecutor(createSuite()), loader); try { List<String> q = e.call(); Events.applyEvents(q, rc, this.getDescription()); } catch (Exception ex) { throw Unchecked.translateCheckedException(ex); } } private void executeInCurrentLoader(final ResultCollector rc) { final TestNGAdapter listener = new TestNGAdapter(this.clazz, this.getDescription(), rc); final XmlSuite suite = createSuite(); TESTNG.setDefaultSuiteName(suite.getName()); TESTNG.setXmlSuites(Collections.singletonList(suite)); LISTENER.setChild(listener); try { TESTNG.run(); } finally { // yes this is hideous LISTENER.setChild(null); } } private XmlSuite createSuite() { final XmlSuite suite = new XmlSuite(); suite.setName(this.clazz.getName()); suite.setSkipFailedInvocationCounts(true); final XmlTest test = new XmlTest(suite); test.setName(this.clazz.getName()); final XmlClass xclass = new XmlClass(this.clazz.getName()); test.setXmlClasses(Collections.singletonList(xclass)); if (!this.config.getExcludedGroups().isEmpty()) { suite.setExcludedGroups(this.config.getExcludedGroups()); } if (!this.config.getIncludedGroups().isEmpty()) { suite.setIncludedGroups(this.config.getIncludedGroups()); } return suite; } } class FailFast implements IInvokedMethodListener { private final FailureTracker listener; FailFast(FailureTracker listener) { this.listener = listener; } @Override public void beforeInvocation(IInvokedMethod method, ITestResult testResult) { if (listener.hasHadFailure()) { throw new SkipException("Skipping"); } } @Override public void afterInvocation(IInvokedMethod method, ITestResult testResult) { } } class MutableTestListenerWrapper implements ITestListener, FailureTracker { private TestNGAdapter child; public void setChild(TestNGAdapter child) { this.child = child; } public boolean hasHadFailure() { return child.hasHadFailure(); } public void onTestStart(ITestResult result) { child.onTestStart(result); } public void onTestSuccess(ITestResult result) { child.onTestSuccess(result); } public void onTestFailure(ITestResult result) { child.onTestFailure(result); } public void onTestSkipped(ITestResult result) { child.onTestSkipped(result); } public void onTestFailedButWithinSuccessPercentage(ITestResult result) { child.onTestFailedButWithinSuccessPercentage(result); } public void onStart(ITestContext context) { child.onStart(context); } public void onFinish(ITestContext context) { child.onFinish(context); } }