blob: 415b96f581f6daee5761d8f09b84dc1aefda9d48 [file] [log] [blame]
package org.testng;
import java.io.EOFException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.xml.parsers.ParserConfigurationException;
import org.testng.internal.ClassHelper;
import org.testng.internal.HostFile;
import org.testng.internal.Invoker;
import org.testng.internal.Utils;
import org.testng.internal.annotations.DefaultAnnotationTransformer;
import org.testng.internal.annotations.IAnnotationFinder;
import org.testng.internal.annotations.IAnnotationTransformer;
import org.testng.internal.annotations.ITest;
import org.testng.internal.annotations.JDK14AnnotationFinder;
import org.testng.internal.remote.SlavePool;
import org.testng.internal.thread.IPooledExecutor;
import org.testng.internal.thread.ThreadUtil;
import org.testng.internal.version.VersionInfo;
import org.testng.log4testng.Logger;
import org.testng.remote.ConnectionInfo;
import org.testng.remote.RemoteSuiteWorker;
import org.testng.remote.RemoteTestWorker;
import org.testng.reporters.EmailableReporter;
import org.testng.reporters.FailedReporter;
import org.testng.reporters.SuiteHTMLReporter;
import org.testng.xml.Parser;
import org.testng.xml.XmlClass;
import org.testng.xml.XmlSuite;
import org.testng.xml.XmlTest;
import org.xml.sax.SAXException;
/**
* This class is the main entry point for running tests in the TestNG framework.
* Users can create their own TestNG object and invoke it in many different
* ways:
* <ul>
* <li>On an existing testng.xml
* <li>On a synthetic testng.xml, created entirely from Java
* <li>By directly setting the test classes
* </ul>
* You can also define which groups to include or exclude, assign parameters, etc...
* <P/>
* The command line parameters are:
* <UL>
* <LI>-d <TT>outputdir</TT>: specify the output directory</LI>
* <LI>-testclass <TT>class_name</TT>: specifies one or several class names </li>
* <LI>-testjar <TT>jar_name</TT>: specifies the jar containing the tests</LI>
* <LI>-sourcedir <TT>src1;src2</TT>: ; separated list of source directories
* (used only when javadoc annotations are used)</LI>
* <LI>-target</LI>
* <LI>-groups</LI>
* <LI>-testrunfactory</LI>
* <LI>-listener</LI>
* </UL>
* <P/>
* Please consult documentation for more details.
*
* FIXME: should support more than simple paths for suite xmls
*
* @see #usage()
*
* @author <a href = "mailto:cedric&#64;beust.com">Cedric Beust</a>
* @author <a href = "mailto:the_mindstorm&#64;evolva.ro">Alex Popescu</a>
*/
public class TestNG {
/** This class' log4testng Logger. */
private static final Logger LOGGER = Logger.getLogger(TestNG.class);
/** The default name for a suite launched from the command line */
public static final String DEFAULT_SUITE_NAME = "Command line suite";
/** The default name for a test launched from the command line */
public static final String DEFAULT_TEST_NAME = "Command line test";
/** The default name of the result's output directory. */
public static final String DEFAULT_OUTPUTDIR = "test-output";
/** A separator constant (semi-colon). */
public static final String SRC_SEPARATOR = ";";
/** The JDK50 annotation type ID ("JDK5").*/
public static final String JDK5_ANNOTATION_TYPE = "JDK5";
/** The JavaDoc annotation type ID ("javadoc"). */
public static final String JAVADOC_ANNOTATION_TYPE = "javadoc";
private static TestNG m_instance;
/** Indicates the TestNG JAR version (JDK 1.4 or JDK 5.0+). */
private static final boolean m_isJdk14 = VersionInfo.IS_JDK14;
protected List<XmlSuite> m_suites = new ArrayList<XmlSuite>();
protected List<XmlSuite> m_cmdlineSuites;
protected String m_outputDir = DEFAULT_OUTPUTDIR;
protected String[] m_sourceDirs;
/** The annotation type for suites/tests that have not explicitly set this attribute. */
protected String m_target = m_isJdk14 ? JAVADOC_ANNOTATION_TYPE : JDK5_ANNOTATION_TYPE;
protected IAnnotationFinder m_javadocAnnotationFinder;
protected IAnnotationFinder m_jdkAnnotationFinder;
protected String[] m_includedGroups;
protected String[] m_excludedGroups;
private Boolean m_isJUnit = Boolean.FALSE;
protected boolean m_useDefaultListeners = true;
protected ITestRunnerFactory m_testRunnerFactory;
// These listeners can be overridden from the command line
protected List<ITestListener> m_testListeners = new ArrayList<ITestListener>();
protected List<ISuiteListener> m_suiteListeners = new ArrayList<ISuiteListener>();
private List<IReporter> m_reporters = new ArrayList<IReporter>();
public static final int HAS_FAILURE = 1;
public static final int HAS_SKIPPED = 2;
public static final int HAS_FSP = 4;
public static final int HAS_NO_TEST = 8;
protected int m_status;
protected boolean m_hasTests= false;
/** The port on which this client will listen. */
private int m_clientPort = 0;
/** The name of the file containing the list of hosts where distributed
* tests will be dispatched. */
private String m_hostFile;
private SlavePool m_slavePool = new SlavePool();
// Command line suite parameters
private int m_threadCount;
private boolean m_useThreadCount;
private String m_parallelMode;
private boolean m_useParallelMode;
private Class[] m_commandLineTestClasses;
/**
* Default constructor. Setting also usage of default listeners/reporters.
*/
public TestNG() {
init(true);
}
/**
* Used by maven2 to have 0 output of any kind come out
* of testng.
* @param useDefaultListeners Whether or not any default reports
* should be added to tests.
*/
public TestNG(boolean useDefaultListeners) {
init(useDefaultListeners);
}
private void init(boolean useDefaultListeners) {
m_instance = this;
m_useDefaultListeners = useDefaultListeners;
}
/**
* @deprecated
*/
@Deprecated
public static TestNG getDefault() {
return m_instance;
}
public int getStatus() {
return m_status;
}
protected void setStatus(int status) {
m_status |= status;
}
/**
* Sets the output directory where the reports will be created.
* @param outputdir The directory.
*/
public void setOutputDirectory(final String outputdir) {
if ((null != outputdir) && !"".equals(outputdir)) {
m_outputDir = outputdir;
}
}
/**
* If this method is passed true before run(), the default listeners
* will not be used.
* <ul>
* <li>org.testng.reporters.TestHTMLReporter
* <li>org.testng.reporters.JUnitXMLReporter
* </ul>
*
* @see org.testng.reporters.TestHTMLReporter
* @see org.testng.reporters.JUnitXMLReporter
*/
public void setUseDefaultListeners(boolean useDefaultListeners) {
m_useDefaultListeners = useDefaultListeners;
}
/**
* The default annotation type for suites that have not explicitly set the annotation property.
* The target is used only in JDK5+.
* @param target the default annotation type (JAVADOC_ANNOTATION_TYPE or JDK5_ANNOTATION_TYPE).
* For backward compatibility reasons we accept "1.4", "1.5" and any other value defaults to
* JDK5_ANNOTATION_TYPE.
*/
public void setTarget(final String target) {
// Target is used only in JDK 1.5 and may get null in JDK 1.4
if (null == target) {
return;
}
// The following switch block could be simplified with three test but the intent
// is to log at different levels when warning.
if (target.equals(JAVADOC_ANNOTATION_TYPE)) {
m_target = JAVADOC_ANNOTATION_TYPE;
}
else if (target.equals(JDK5_ANNOTATION_TYPE)) {
m_target = JDK5_ANNOTATION_TYPE;
}
else if (target.equals("1.4")
|| target.toLowerCase().equals(JAVADOC_ANNOTATION_TYPE.toLowerCase())) {
// For backward compatibility only
// Log at info level
m_target = JAVADOC_ANNOTATION_TYPE;
log("Illegal target type " + target + " defaulting to " + JAVADOC_ANNOTATION_TYPE);
}
else if ("1.5".equals(target)
|| target.toLowerCase().equals(JDK5_ANNOTATION_TYPE.toLowerCase())) {
// For backward compatibility only
// Log at info level
m_target = JDK5_ANNOTATION_TYPE;
log("Illegal target type " + target + " defaulting to " + JDK5_ANNOTATION_TYPE);
}
else if (target.toLowerCase().equals("jdk15")) {
// For backward compatibility only
// Log at info level
m_target = JDK5_ANNOTATION_TYPE;
log("Illegal target type " + target + " defaulting to " + JDK5_ANNOTATION_TYPE);
}
else {
// For backward compatibility only
// Log at warn level
// TODO should we make this an error?
m_target = JDK5_ANNOTATION_TYPE;
log("Illegal target type " + target + " defaulting to " + JDK5_ANNOTATION_TYPE);
}
}
/**
* Sets the ; separated path of source directories. This is used only with JavaDoc type
* annotations. The directories do not have to be the root of a class hierarchy. For
* example, "c:\java\src\org\testng" is a valid directory.
*
* If a resource named "testng-sourcedir-override.properties" is found in the classpath,
* it will override this call. "testng-sourcedir-override.properties" must contain a
* sourcedir property initialized with a semi-colon list of directories. For example:
*
* sourcedir=c:\java\src\org\testng;D:/dir2
*
* Note that for the override to occur, this method must be called. i.e. it is not sufficient
* to place "testng-sourcedir-override.properties" in the classpath.
*
* @param sourcePaths a semi-colon separated list of source directories.
*/
public void setSourcePath(String sourcePaths) {
// Start of patch specific code
// This is an optimization to reduce the sourcePath scope
// Is it OK to look only for the Thread context class loader?
InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("testng-sourcedir-override.properties");
// Resource exists. Use override values and ignore given value
if (is != null) {
Properties props = new Properties();
try {
props.load(is);
}
catch (IOException e) {
throw new RuntimeException(e);
}
sourcePaths = props.getProperty("sourcedir");
}
if (null == sourcePaths || "".equals(sourcePaths.trim())) {
return;
}
m_sourceDirs = Utils.split(sourcePaths, SRC_SEPARATOR);
}
/**
* Sets a jar containing a testng.xml file.
*
* @param jarPath
*/
public void setTestJar(String jarPath) {
if ((null == jarPath) || "".equals(jarPath)) {
return;
}
File jarFile = new File(jarPath);
try {
URL jarfile = new URL("jar", "", "file:" + jarFile.getAbsolutePath() + "!/");
URLClassLoader jarLoader = new URLClassLoader(new URL[] { jarfile });
Thread.currentThread().setContextClassLoader(jarLoader);
m_suites.addAll(new Parser().parse());
}
catch(MalformedURLException mfurle) {
System.err.println("could not find jar file named: " + jarFile.getAbsolutePath());
}
catch(IOException ioe) {
System.out.println("An exception occurred while trying to load testng.xml from within jar "
+ jarFile.getAbsolutePath());
}
catch(SAXException saxe) {
System.out.println("testng.xml from within jar "
+ jarFile.getAbsolutePath()
+ " is not well formatted");
saxe.printStackTrace(System.out);
}
catch(ParserConfigurationException pce) {
pce.printStackTrace(System.out);
}
}
/**
* Define the number of threads in the thread pool.
*/
public void setThreadCount(int threadCount) {
if(threadCount < 1) {
exitWithError("Cannot use a threadCount parameter less than 1; 1 > " + threadCount);
}
m_threadCount = threadCount;
m_useThreadCount = true;
}
/**
* Define whether this run will be run in parallel mode.
*/
public void setParallel(String parallel) {
m_parallelMode = parallel;
m_useParallelMode = true;
}
public void setCommandLineSuite(XmlSuite suite) {
m_cmdlineSuites = new ArrayList<XmlSuite>();
m_cmdlineSuites.add(suite);
m_suites.add(suite);
}
/**
* Set the test classes to be run by this TestNG object. This method
* will create a dummy suite that will wrap these classes called
* "Command Line Test".
* <p/>
* If used together with threadCount, parallel, groups, excludedGroups than this one must be set first.
*
* @param classes An array of classes that contain TestNG annotations.
*/
public void setTestClasses(Class[] classes) {
m_commandLineTestClasses = classes;
}
private IAnnotationFinder getAnnotationFinder() {
return JDK5_ANNOTATION_TYPE.equals(m_target)
? m_jdkAnnotationFinder
: m_javadocAnnotationFinder;
}
private List<XmlSuite> createCommandLineSuites(Class[] classes) {
//
// See if any of the classes has an xmlSuite or xmlTest attribute.
// If it does, create the appropriate XmlSuite, otherwise, create
// the default one
//
XmlClass[] xmlClasses = Utils.classesToXmlClasses(classes);
Map<String, XmlSuite> suites = new HashMap<String, XmlSuite>();
IAnnotationFinder finder = getAnnotationFinder();
for (int i = 0; i < classes.length; i++) {
Class c = classes[i];
ITest test = (ITest) finder.findAnnotation(c, ITest.class);
String suiteName = DEFAULT_SUITE_NAME;
String testName = DEFAULT_TEST_NAME;
if (test != null) {
suiteName = test.getSuiteName();
testName = test.getTestName();
}
XmlSuite xmlSuite = suites.get(suiteName);
if (xmlSuite == null) {
xmlSuite = new XmlSuite();
xmlSuite.setName(suiteName);
suites.put(suiteName, xmlSuite);
}
XmlTest xmlTest = null;
for (XmlTest xt : xmlSuite.getTests()) {
if (xt.getName().equals(testName)) {
xmlTest = xt;
break;
}
}
if (xmlTest == null) {
xmlTest = new XmlTest(xmlSuite);
xmlTest.setName(testName);
}
xmlTest.getXmlClasses().add(xmlClasses[i]);
}
// XmlSuite[] result =
// (XmlSuite[]) suites.values().toArray(new XmlSuite[suites.size()]);
return new ArrayList<XmlSuite>(suites.values());
}
/**
* Set the suites file names to be run by this TestNG object. This method tries to load and
* parse the specified TestNG suite xml files. If a file is missing, it is ignored.
*
* @param suites A list of paths to one more XML files defining the tests. For example:
*
* <pre>
* TestNG tng = new TestNG();
* List<String> suites = new ArrayList<String>();
* suites.add("c:/tests/testng1.xml");
* suites.add("c:/tests/testng2.xml");
* tng.setTestSuites(suites);
* tng.run();
* </pre>
*/
public void setTestSuites(List<String> suites) {
for (String suiteXmlPath : suites) {
try {
Collection<XmlSuite> allSuites = new Parser(suiteXmlPath).parse();
for (XmlSuite s : allSuites) {
m_suites.add(s);
}
}
catch(FileNotFoundException e) {
e.printStackTrace(System.out);
}
catch(IOException e) {
e.printStackTrace(System.out);
}
catch(ParserConfigurationException e) {
e.printStackTrace(System.out);
}
catch(SAXException e) {
e.printStackTrace(System.out);
}
}
}
/**
* Specifies the XmlSuite objects to run.
* @param suites
* @see org.testng.xml.XmlSuite
*/
public void setXmlSuites(List<XmlSuite> suites) {
m_suites = suites;
}
/**
* Define which groups will be excluded from this run.
*
* @param groups A list of group names separated by a comma.
*/
public void setExcludedGroups(String groups) {
m_excludedGroups = Utils.split(groups, ",");
}
/**
* Define which groups will be included from this run.
*
* @param groups A list of group names separated by a comma.
*/
public void setGroups(String groups) {
m_includedGroups = Utils.split(groups, ",");
}
private void setTestRunnerFactoryClass(Class testRunnerFactoryClass) {
setTestRunnerFactory((ITestRunnerFactory) newInstance(testRunnerFactoryClass));
}
private void setTestRunnerFactory(ITestRunnerFactory itrf) {
m_testRunnerFactory= itrf;
}
/**
* Define which listeners to user for this run.
*
* @param classes A list of classes, which must be either ISuiteListener,
* ITestListener or IReporter
*/
public void setListenerClasses(List<Class> classes) {
for (Class cls: classes) {
addListener(newInstance(cls));
}
}
private void setListeners(List<Object> itls) {
for (Object obj: itls) {
addListener(obj);
}
}
public void addListener(Object listener) {
if (! (listener instanceof ISuiteListener)
&& ! (listener instanceof ITestListener)
&& ! (listener instanceof IReporter)
&& ! (listener instanceof IAnnotationTransformer))
{
exitWithError("Listener " + listener + " must be one of ITestListener, ISuiteListener, IReporter or IAnnotationTransformer");
}
else {
if (listener instanceof ISuiteListener) {
addListener((ISuiteListener) listener);
}
if (listener instanceof ITestListener) {
addListener((ITestListener) listener);
}
if (listener instanceof IReporter) {
addListener((IReporter) listener);
}
if (listener instanceof IAnnotationTransformer) {
setAnnotationTransformer((IAnnotationTransformer) listener);
}
}
}
public void addListener(ISuiteListener listener) {
if (null != listener) {
m_suiteListeners.add(listener);
}
}
public void addListener(ITestListener listener) {
if (null != listener) {
m_testListeners.add(listener);
}
}
public void addListener(IReporter listener) {
if (null != listener) {
m_reporters.add(listener);
}
}
public List<IReporter> getReporters() {
return m_reporters;
}
public List<ITestListener> getTestListeners() {
return m_testListeners;
}
public List<ISuiteListener> getSuiteListeners() {
return m_suiteListeners;
}
/** The verbosity level. TODO why not a simple int? */
private Integer m_verbose;
private IAnnotationTransformer m_annotationTransformer
= new DefaultAnnotationTransformer();
/**
* Sets the level of verbosity. This value will override the value specified
* in the test suites.
*
* @param verbose the verbosity level (0 to 10 where 10 is most detailed)
* Actually, this is a lie: you can specify -1 and this will put TestNG
* in debug mode (no longer slicing off stack traces and all).
*/
public void setVerbose(int verbose) {
m_verbose = new Integer(verbose);
}
private void initializeCommandLineSuites() {
if(null != m_commandLineTestClasses) {
m_cmdlineSuites = createCommandLineSuites(m_commandLineTestClasses);
for (XmlSuite s : m_cmdlineSuites) {
m_suites.add(s);
}
}
}
private void initializeCommandLineSuitesParams() {
if(null == m_cmdlineSuites) {
return;
}
for (XmlSuite s : m_cmdlineSuites) {
if(m_useThreadCount) {
s.setThreadCount(m_threadCount);
}
if(m_useParallelMode) {
s.setParallel(m_parallelMode);
}
}
}
private void initializeCommandLineSuitesGroups() {
if (null != m_cmdlineSuites) {
for (XmlSuite s : m_cmdlineSuites) {
if(null != m_includedGroups && m_includedGroups.length > 0) {
s.getTests().get(0).setIncludedGroups(Arrays.asList(m_includedGroups));
}
if(null != m_excludedGroups && m_excludedGroups.length > 0) {
s.getTests().get(0).setExcludedGroups(Arrays.asList(m_excludedGroups));
}
}
}
}
private void initializeListeners() {
m_testListeners.add(new ExitCodeListener(this));
if(m_useDefaultListeners) {
m_reporters.add(new SuiteHTMLReporter());
m_reporters.add(new FailedReporter());
m_reporters.add(new EmailableReporter());
}
}
private void initializeAnnotationFinders() {
m_javadocAnnotationFinder= new JDK14AnnotationFinder(getAnnotationTransformer());
if(null != m_sourceDirs) {
m_javadocAnnotationFinder.addSourceDirs(m_sourceDirs);
}
if(!isJdk14()) {
m_jdkAnnotationFinder= ClassHelper.createJdkAnnotationFinder(getAnnotationTransformer());
}
}
/**
* Run TestNG.
*/
public void run() {
initializeListeners();
initializeAnnotationFinders();
initializeCommandLineSuites();
initializeCommandLineSuitesParams();
initializeCommandLineSuitesGroups();
List<ISuite> suiteRunners = null;
//
// Slave mode
//
if (m_clientPort != 0) {
waitForSuites();
}
//
// Regular mode
//
else if (m_hostFile == null) {
suiteRunners = runSuitesLocally();
}
//
// Master mode
//
else {
suiteRunners = runSuitesRemotely();
}
if(null != suiteRunners) {
generateReports(suiteRunners);
}
if(!m_hasTests) {
setStatus(HAS_NO_TEST);
if (TestRunner.getVerbose() > 1) {
System.err.println("[TestNG] No tests found. Nothing was run");
}
}
}
private void generateReports(List<ISuite> suiteRunners) {
for (IReporter reporter : m_reporters) {
try {
reporter.generateReport(m_suites, suiteRunners, m_outputDir);
}
catch(Exception ex) {
System.err.println("[TestNG] Reporter " + reporter + " failed");
ex.printStackTrace(System.err);
}
}
}
private static ConnectionInfo resetSocket(int clientPort, ConnectionInfo oldCi)
throws IOException
{
ConnectionInfo result = new ConnectionInfo();
ServerSocket serverSocket = new ServerSocket(clientPort);
serverSocket.setReuseAddress(true);
log("Waiting for connections on port " + clientPort);
Socket socket = serverSocket.accept();
result.setSocket(socket);
return result;
}
/**
* Invoked in client mode. In this case, wait for a connection
* on the given port, run the XmlSuite we received and return the SuiteRunner
* created to run it.
* @throws IOException
*/
private void waitForSuites() {
try {
ConnectionInfo ci = resetSocket(m_clientPort, null);
while (true) {
try {
XmlSuite s = (XmlSuite) ci.getOis().readObject();
log("Processing " + s.getName());
m_suites = new ArrayList<XmlSuite>();
m_suites.add(s);
List<ISuite> suiteRunners = runSuitesLocally();
ISuite sr = suiteRunners.get(0);
log("Done processing " + s.getName());
ci.getOos().writeObject(sr);
}
catch (ClassNotFoundException e) {
e.printStackTrace(System.out);
}
catch(EOFException ex) {
log("Connection closed " + ex.getMessage());
ci = resetSocket(m_clientPort, ci);
}
catch(SocketException ex) {
log("Connection closed " + ex.getMessage());
ci = resetSocket(m_clientPort, ci);
}
}
}
catch(IOException ex) {
ex.printStackTrace(System.out);
}
}
private static void log(String string) {
Utils.log("", 2, string);
}
private List<ISuite> runSuitesRemotely() {
List<ISuite> result = new ArrayList<ISuite>();
HostFile hostFile = new HostFile(m_hostFile);
//
// Create one socket per host found
//
String[] hosts = hostFile.getHosts();
Socket[] sockets = new Socket[hosts.length];
for (int i = 0; i < hosts.length; i++) {
String host = hosts[i];
String[] s = host.split(":");
try {
sockets[i] = new Socket(s[0], Integer.parseInt(s[1]));
}
catch (NumberFormatException e) {
e.printStackTrace(System.out);
}
catch (UnknownHostException e) {
e.printStackTrace(System.out);
}
catch (IOException e) {
Utils.error("Couldn't connect to " + host + ": " + e.getMessage());
}
}
//
// Add these hosts to the pool
//
try {
m_slavePool.addSlaves(sockets);
}
catch (IOException e1) {
e1.printStackTrace(System.out);
}
//
// Dispatch the suites/tests to each host
//
List<Runnable> workers = new ArrayList<Runnable>();
//
// Send one XmlTest at a time to remote hosts
//
if (hostFile.isStrategyTest()) {
for (XmlSuite suite : m_suites) {
suite.setVerbose(hostFile.getVerbose());
SuiteRunner suiteRunner =
new SuiteRunner(suite, m_outputDir, new IAnnotationFinder[] {m_javadocAnnotationFinder, m_jdkAnnotationFinder});
for (XmlTest test : suite.getTests()) {
XmlSuite tmpSuite = new XmlSuite();
tmpSuite.setXmlPackages(suite.getXmlPackages());
tmpSuite.setAnnotations(suite.getAnnotations());
tmpSuite.setJUnit(suite.isJUnit());
tmpSuite.setName("Temporary suite for " + test.getName());
tmpSuite.setParallel(suite.getParallel());
tmpSuite.setParameters(suite.getParameters());
tmpSuite.setThreadCount(suite.getThreadCount());
tmpSuite.setVerbose(suite.getVerbose());
XmlTest tmpTest = new XmlTest(tmpSuite);
tmpTest.setAnnotations(test.getAnnotations());
tmpTest.setBeanShellExpression(test.getExpression());
tmpTest.setClassNames(test.getXmlClasses());
tmpTest.setExcludedGroups(test.getExcludedGroups());
tmpTest.setIncludedGroups(test.getIncludedGroups());
tmpTest.setJUnit(test.isJUnit());
tmpTest.setMethodSelectors(test.getMethodSelectors());
tmpTest.setName(test.getName());
tmpTest.setParallel(test.getParallel());
tmpTest.setParameters(test.getParameters());
tmpTest.setVerbose(test.getVerbose());
tmpTest.setXmlClasses(test.getXmlClasses());
tmpTest.setXmlPackages(test.getXmlPackages());
workers.add(new RemoteTestWorker(tmpSuite, m_slavePool, suiteRunner, result));
}
result.add(suiteRunner);
}
}
//
// Send one XmlSuite at a time to remote hosts
//
else {
for (XmlSuite suite : m_suites) {
workers.add(new RemoteSuiteWorker(suite, m_slavePool, result));
}
}
//
// Launch all the workers
//
IPooledExecutor executor= ThreadUtil.createPooledExecutor(1);
for (Runnable r : workers) {
executor.execute(r);
}
//
// Wait for completion
//
executor.shutdown();
// TODO(cbeust)
// Need to make this configurable
long maxTimeOut= 10 * 1000; // 10 minutes
try {
executor.awaitTermination(maxTimeOut);
}
catch (InterruptedException e) {
e.printStackTrace(System.out);
}
//
// Run test listeners
//
for (ISuite suite : result) {
for (ISuiteResult suiteResult : suite.getResults().values()) {
Collection<ITestResult> allTests[] = new Collection[] {
suiteResult.getTestContext().getPassedTests().getAllResults(),
suiteResult.getTestContext().getFailedTests().getAllResults(),
suiteResult.getTestContext().getSkippedTests().getAllResults(),
suiteResult.getTestContext().getFailedButWithinSuccessPercentageTests().getAllResults(),
};
for (Collection<ITestResult> all : allTests) {
for (ITestResult tr : all) {
Invoker.runTestListeners(tr, m_testListeners);
}
}
}
}
return result;
}
/**
* This needs to be public for maven2, for now..At least
* until an alternative mechanism is found.
* @return
*/
public List<ISuite> runSuitesLocally() {
List<ISuite> result = new ArrayList<ISuite>();
int v = TestRunner.getVerbose();
if (TestRunner.getVerbose() > 0) {
StringBuffer allFiles = new StringBuffer();
for (XmlSuite s : m_suites) {
allFiles.append(" ").append(s.getFileName()).append("\n");
}
Utils.log("Parser", 0, "Running:\n" + allFiles.toString());
}
if (m_suites.size() > 0) {
for (XmlSuite xmlSuite : m_suites) {
xmlSuite.setDefaultAnnotations(m_target);
if (null != m_isJUnit) {
xmlSuite.setJUnit(m_isJUnit);
}
// TODO CQ is this OK? Should the command line verbose flag override
// what is explicitly specified in the suite?
if (null != m_verbose) {
xmlSuite.setVerbose(m_verbose);
}
result.add(createAndRunSuiteRunners(xmlSuite));
}
}
else {
setStatus(HAS_NO_TEST);
System.err.println("[ERROR]: No test suite found. Nothing to run");
}
//
// Generate the suites report
//
return result;
}
protected SuiteRunner createAndRunSuiteRunners(XmlSuite xmlSuite) {
SuiteRunner result = new SuiteRunner(xmlSuite,
m_outputDir,
m_testRunnerFactory,
m_useDefaultListeners,
new IAnnotationFinder[] {m_javadocAnnotationFinder, m_jdkAnnotationFinder});
for (ISuiteListener isl : m_suiteListeners) {
result.addListener(isl);
}
result.setTestListeners(m_testListeners);
// Set the hostname, if any
if (m_clientPort != 0) {
try {
result.setHost(InetAddress.getLocalHost() + ":" + m_clientPort);
}
catch (UnknownHostException e) {
e.printStackTrace(System.out);
}
}
result.run();
return result;
}
private Object newInstance(Class clazz) {
try {
Object instance = clazz.newInstance();
return instance;
}
catch(IllegalAccessException iae) {
throw new TestNGException("Class "
+ clazz.getName()
+ " does not have a no-args constructor",
iae);
}
catch(InstantiationException ie) {
throw new TestNGException("Cannot instantiate class "
+ clazz.getName(),
ie);
}
catch(ExceptionInInitializerError eiierr) {
throw new TestNGException("An exception occurred in static initialization of class "
+ clazz.getName(),
eiierr);
}
catch(SecurityException se) {
throw new TestNGException(se);
}
}
/**
* The TestNG entry point for command line execution.
*
* @param argv the TestNG command line parameters.
*/
public static void main(String[] argv) {
TestNG testng = privateMain(argv, null);
System.exit(testng.getStatus());
}
/**
* TODO cquezel JavaDoc.
*
* @param argv
* @param listener
* @return
*/
public static TestNG privateMain(String[] argv, ITestListener listener) {
Map cmdLineArgs = TestNGCommandLineArgs.parseCommandLine(argv);
TestNG result = new TestNG();
if (null != listener) {
result.addListener(listener);
}
try {
checkConditions(cmdLineArgs);
{
Integer verbose = (Integer) cmdLineArgs.get(TestNGCommandLineArgs.LOG);
if (null != verbose) {
result.setVerbose(verbose.intValue());
}
}
result.setOutputDirectory((String) cmdLineArgs.get(TestNGCommandLineArgs.OUTDIR_COMMAND_OPT));
result.setSourcePath((String) cmdLineArgs.get(TestNGCommandLineArgs.SRC_COMMAND_OPT));
result.setTarget((String) cmdLineArgs.get(TestNGCommandLineArgs.TARGET_COMMAND_OPT));
List<String> testClasses = (List<String>) cmdLineArgs.get(TestNGCommandLineArgs.TESTCLASS_COMMAND_OPT);
if (null != testClasses) {
Class[] classes = (Class[]) testClasses.toArray(new Class[testClasses.size()]);
result.setTestClasses(classes);
}
List<String> testNgXml =
(List<String>) cmdLineArgs.get(TestNGCommandLineArgs.SUITE_DEF_OPT);
if (null != testNgXml) {
result.setTestSuites(testNgXml);
}
String useDefaultListeners =
(String) cmdLineArgs.get(TestNGCommandLineArgs.USE_DEFAULT_LISTENERS);
if (null != useDefaultListeners) {
result.setUseDefaultListeners("true".equalsIgnoreCase(useDefaultListeners));
}
result.setGroups((String) cmdLineArgs.get(TestNGCommandLineArgs.GROUPS_COMMAND_OPT));
result.setExcludedGroups((String) cmdLineArgs.get(TestNGCommandLineArgs.EXCLUDED_GROUPS_COMMAND_OPT));
result.setTestJar((String) cmdLineArgs.get(TestNGCommandLineArgs.TESTJAR_COMMAND_OPT));
result.setJUnit((Boolean) cmdLineArgs.get(TestNGCommandLineArgs.JUNIT_DEF_OPT));
result.setHostFile((String) cmdLineArgs.get(TestNGCommandLineArgs.HOSTFILE_OPT));
String threadCount = (String) cmdLineArgs.get(TestNGCommandLineArgs.THREAD_COUNT);
if (threadCount != null) {
result.setThreadCount(Integer.parseInt(threadCount));
}
String client = (String) cmdLineArgs.get(TestNGCommandLineArgs.SLAVE_OPT);
if (client != null) {
result.setClientPort(Integer.parseInt(client));
}
List<Class> listenerClasses =
(List<Class>) cmdLineArgs.get(TestNGCommandLineArgs.LISTENER_COMMAND_OPT);
if (null != listenerClasses) {
result.setListenerClasses(listenerClasses);
}
result.run();
}
catch(TestNGException ex) {
if (TestRunner.getVerbose() > 1) {
ex.printStackTrace(System.out);
}
else {
System.err.println("[ERROR]: " + ex.getMessage());
}
System.exit(1);
}
return result;
}
private void setClientPort(int clientPort) {
m_clientPort = clientPort;
}
/**
* Set the path to the file that contains the list of slaves.
* @param hostFile
*/
public void setHostFile(String hostFile) {
m_hostFile = hostFile;
}
/**
* Specify if this run should be made in JUnit mode
*
* @param isJUnit
*/
public void setJUnit(Boolean isJUnit) {
m_isJUnit = isJUnit;
}
/**
* @deprecated The TestNG version is now established at load time. This
* method is not required anymore and is now a no-op.
*/
@Deprecated
public static void setTestNGVersion() {
LOGGER.info("setTestNGVersion has been deprecated.");
}
/**
* Returns true if this is the JDK 1.4 JAR version of TestNG, false otherwise.
*
* @return true if this is the JDK 1.4 JAR version of TestNG, false otherwise.
*/
public static boolean isJdk14() {
return m_isJdk14;
}
/**
* Checks TestNG preconditions. For example, this method makes sure that if this is the
* JDK 1.4 version of TestNG, a source directory has been specified. This method calls
* System.exit(-1) or throws an exception if the preconditions are not satisfied.
*
* @param params the parsed command line parameters.
*/
private static void checkConditions(Map params) {
// TODO CQ document why sometimes we throw exceptions and sometimes we exit.
List<String> testClasses = (List<String>) params.get(TestNGCommandLineArgs.TESTCLASS_COMMAND_OPT);
List<String> testNgXml = (List<String>) params.get(TestNGCommandLineArgs.SUITE_DEF_OPT);
Object testJar = params.get(TestNGCommandLineArgs.TESTJAR_COMMAND_OPT);
Object port = params.get(TestNGCommandLineArgs.SLAVE_OPT);
if (testClasses == null && testNgXml == null && port == null && testJar == null) {
System.err.println("You need to specify at least one testng.xml or one class");
usage();
System.exit(-1);
}
if (isJdk14()) {
String srcPath = (String) params.get(TestNGCommandLineArgs.SRC_COMMAND_OPT);
if ((null == srcPath) || "".equals(srcPath)) {
throw new TestNGException("No sourcedir was specified");
}
}
String groups = (String) params.get(TestNGCommandLineArgs.GROUPS_COMMAND_OPT);
String excludedGroups = (String) params.get(TestNGCommandLineArgs.EXCLUDED_GROUPS_COMMAND_OPT);
if ((null != groups || null != excludedGroups) && null == testClasses) {
throw new TestNGException("Groups option should be used with testclass option");
}
}
private static void ppp(String s) {
System.out.println("[TestNG] " + s);
}
/**
* @return true if at least one test failed.
*/
public boolean hasFailure() {
return (getStatus() & HAS_FAILURE) == HAS_FAILURE;
}
/**
* @deprecated
*/
@Deprecated
public void setHasFailure(boolean hasFailure) {
m_status |= HAS_FAILURE;
}
/**
* @return true if at least one test failed within success percentage.
*/
public boolean hasFailureWithinSuccessPercentage() {
return (getStatus() & HAS_FSP) == HAS_FSP;
}
/**
* @deprecated
*/
@Deprecated
public void setHasFailureWithinSuccessPercentage(boolean hasFailureWithinSuccessPercentage) {
m_status |= HAS_FSP;
}
/**
* @return true if at least one test was skipped.
*/
public boolean hasSkip() {
return (getStatus() & HAS_SKIPPED) == HAS_SKIPPED;
}
/**
* @deprecated
*/
@Deprecated
public void setHasSkip(boolean hasSkip) {
m_status |= HAS_SKIPPED;
}
/**
* Prints the usage message to System.out. This message describes all the command line
* options.
*/
public static void usage() {
TestNGCommandLineArgs.usage();
}
static void exitWithError(String msg) {
System.err.println(msg);
usage();
System.exit(1);
}
public String getOutputDirectory() {
return m_outputDir;
}
public IAnnotationTransformer getAnnotationTransformer() {
return m_annotationTransformer;
}
public void setAnnotationTransformer(IAnnotationTransformer t) {
m_annotationTransformer = t;
}
public static class ExitCodeListener implements ITestListener {
protected TestNG m_mainRunner;
public ExitCodeListener() {
m_mainRunner = TestNG.m_instance;
}
public ExitCodeListener(TestNG runner) {
m_mainRunner = runner;
}
public void onTestFailure(ITestResult result) {
m_mainRunner.m_status |= HAS_FAILURE;
}
public void onTestSkipped(ITestResult result) {
m_mainRunner.m_status |= HAS_SKIPPED;
}
public void onTestFailedButWithinSuccessPercentage(ITestResult result) {
m_mainRunner.m_status |= HAS_FSP;
}
public void onTestSuccess(ITestResult result) {
}
public void onStart(ITestContext context) {
}
public void onFinish(ITestContext context) {
}
public void onTestStart(ITestResult result) {
setHasRunTests();
}
private void setHasRunTests() {
m_mainRunner.m_hasTests= true;
}
}
}