Java tutorial
/** * This file is part of PaxmlTestNG. * * PaxmlTestNG is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * PaxmlTestNG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with PaxmlTestNG. If not, see <http://www.gnu.org/licenses/>. */ package org.paxml.testng; import java.io.File; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import javassist.ClassPool; import javassist.CtClass; import javassist.CtConstructor; import javassist.CtNewConstructor; import javassist.Modifier; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.paxml.core.Context; import org.paxml.launch.LaunchModel; import org.paxml.launch.LaunchPoint; import org.paxml.launch.Matcher; import org.paxml.launch.Paxml; import org.paxml.tag.AbstractTag; import org.paxml.testng.AbstractPaxmlTestResult.ResultType; import org.testng.annotations.Factory; import org.testng.annotations.Optional; import org.testng.annotations.Parameters; /** * paxml test case factory for TestNG. * * @author Xuetao Niu * */ public class PaxmlTestCaseFactory { private static final AtomicInteger SEQUENCE = new AtomicInteger(0); /** * The paxml launch plan file path. */ public static final String PARAM_NAME_PLANFILE = "paxmlTestPlanFile"; public static final String PARAM_NAME_SUPPRESS_GROUPS = "paxmlSuppressGroups"; public static final String PARAM_NAME_RESULT_DIR = "paxmlTestResultDir"; public static final String PARAM_NAME_RESULT_TYPE = "paxmlTestResultType"; private static final Log log = LogFactory.getLog(PaxmlTestCaseFactory.class); private static final Object LOCK = new Object(); // this hashmap is accessed only from synchronization block, so no need to // have extra synchronization nor be concurrent hash map. private static final Map<String, Constructor<? extends PaxmlTestCase>> CACHE = new HashMap<String, Constructor<? extends PaxmlTestCase>>(); public static interface ILockedOperation<T> { T perform(); } /** * The factory method. * * @param planFile * the plan file * @param suppressedGroups * the execution groups to suppress * @param outputDir * the dir to output results * @param resultType * the format of results * @return the test objects. */ @Factory @Parameters({ PARAM_NAME_PLANFILE, PARAM_NAME_SUPPRESS_GROUPS, PARAM_NAME_RESULT_DIR, PARAM_NAME_RESULT_TYPE }) public Object[] create(final String planFile, @Optional("") final String suppressedGroups, @Optional("./target/surefire-reports/paxml/results") final String outputDir, @Optional("JSON") final String resultType) { final List<Matcher> suppression = new ArrayList<Matcher>(0); for (String groupName : AbstractTag.parseDelimitedString(suppressedGroups, null)) { Matcher matcher = new Matcher(); matcher.setMatchPath(false); matcher.setPattern(groupName); suppression.add(matcher); } return performLocked(new ILockedOperation<Object[]>() { @Override public Object[] perform() { File resultFolder = null; ResultType rt = null; final long start = System.currentTimeMillis(); long pid = -1; try { File dir = new File(outputDir); rt = ResultType.valueOf(resultType.toUpperCase()); resultFolder = new File(dir, SEQUENCE.getAndIncrement() + "/"); TestCases r = createTestCases(planFile, suppression.isEmpty() ? null : suppression, resultFolder, rt); pid = r.planPid; PaxmlTestCase.init(r.objects.length, start, FilenameUtils.getBaseName(planFile)); // clean the paxml thread context and log into the default // file Context.cleanCurrentThreadContext(); if (log.isInfoEnabled()) { log.info("Launching totally " + r.objects.length + " tests ..."); } return r.objects; } catch (Throwable e) { if (log.isErrorEnabled()) { log.error("Cannot create test cases", e); } return new Object[] { new PaxmlPlanFileFailure(e, planFile, resultFolder, rt, Context.getCurrentContext(), Thread.currentThread().getName(), pid, start, System.currentTimeMillis()) }; } } }); } /** * Let the shared context be propagated to other threads. * * @param <T> * @param op * @return */ public static <T> T performLocked(ILockedOperation<T> op) { synchronized (LOCK) { return op.perform(); } } private class TestCases { Object[] objects; long planPid; } private TestCases createTestCases(String planFile, List<Matcher> suppression, File outputDir, ResultType resultType) { LaunchModel model = Paxml.executePlanFile(planFile, null); List<LaunchPoint> points = model.getLaunchPoints(false, -1); List<Object> result = new LinkedList<Object>(); for (LaunchPoint p : points) { if (isSuppressed(suppression, p.getGroup())) { if (log.isInfoEnabled()) { log.info("This scenario '" + p.getResource().getName() + "' will not run because its group is suppressed: " + p.getGroup()); } } else { result.add(createTestCase(p, outputDir, resultType)); } } if (result.isEmpty()) { if (log.isWarnEnabled()) { log.warn("No scenarios will run from plan file:" + planFile); } } TestCases r = new TestCases(); r.objects = result.toArray(new Object[result.size()]); r.planPid = model.getPlanProcessId(); return r; } private static Object createTestCase(LaunchPoint p, File outputDir, ResultType resultType) { String className = p.getResource().getName(); if (StringUtils.isNoneBlank(p.getGroup())) { className = p.getGroup() + "." + className; } try { Constructor<? extends PaxmlTestCase> constructor = CACHE.get(className); if (constructor == null) { ClassPool pool = ClassPool.getDefault(); if (log.isInfoEnabled()) { log.info("Generating test class proxy:" + className); } CtClass testclass = pool.makeClass(className); final CtClass superClass = pool.get(PaxmlTestCase.class.getName()); testclass.setSuperclass(superClass); testclass.setModifiers(Modifier.PUBLIC); // Add a constructor which will call super( ... ); CtClass[] params = new CtClass[] { pool.get(LaunchPoint.class.getName()), pool.get(File.class.getName()), pool.get(ResultType.class.getName()) }; final CtConstructor ctor = CtNewConstructor.make(params, null, CtNewConstructor.PASS_PARAMS, null, null, testclass); testclass.addConstructor(ctor); Class<? extends PaxmlTestCase> c = testclass.toClass(); constructor = c.getConstructor(new Class[] { LaunchPoint.class, File.class, ResultType.class }); CACHE.put(className, constructor); } return constructor.newInstance(new Object[] { p, outputDir, resultType }); } catch (Exception e) { throw new RuntimeException("Could not create test case: " + className, e); } } private boolean isSuppressed(List<Matcher> suppression, String groupName) { if (suppression == null || suppression.isEmpty()) { return false; } for (Matcher m : suppression) { if (m.match(groupName)) { return true; } } return false; } }