blob: 68c10daee78e0545310dfc2bdbe44bcf253a55c9 [file] [log] [blame]
package org.testng;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import org.testng.internal.ClassHelper;
import org.testng.internal.Utils;
/**
* TestNG/RemoteTestNG command line arguments parser.
*
* @author Cedric Beust
* @author <a href = "mailto:the_mindstorm&#64;evolva.ro">Alexandru Popescu</a>
*/
public final class TestNGCommandLineArgs {
public static final String SHOW_TESTNG_STACK_FRAMES = "testng.show.stack.frames";
public static final String TEST_CLASSPATH = "testng.test.classpath";
/** The test report output directory option. */
public static final String OUTDIR_COMMAND_OPT = "-d";
/** The list of test classes option. */
public static final String TESTCLASS_COMMAND_OPT = "-testclass";
/** */
public static final String TESTJAR_COMMAND_OPT = "-testjar";
/** The source directory option (for JDK1.4). */
public static final String SRC_COMMAND_OPT = "-sourcedir";
// These next two are used by the Eclipse plug-in
public static final String PORT_COMMAND_OPT = "-port";
public static final String HOST_COMMAND_OPT = "-host";
/** The logging level option. */
public static final String LOG = "-log";
/** The default target option (for JDK 5.0+) only. */
public static final String TARGET_COMMAND_OPT = "-target";
public static final String GROUPS_COMMAND_OPT = "-groups";
public static final String EXCLUDED_GROUPS_COMMAND_OPT = "-excludegroups";
public static final String TESTRUNNER_FACTORY_COMMAND_OPT = "-testrunfactory";
public static final String LISTENER_COMMAND_OPT = "-listener";
public static final String SUITE_DEF_OPT = "testng.suite.definitions";
public static final String JUNIT_DEF_OPT = "-junit";
public static final String SLAVE_OPT = "-slave";
public static final String HOSTFILE_OPT = "-hostfile";
public static final String THREAD_COUNT = "-threadcount";
/**
* When given a file name to form a class name, the file name is parsed and divided
* into segments. For example, "c:/java/classes/com/foo/A.class" would be divided
* into 6 segments {"C:" "java", "classes", "com", "foo", "A"}. The first segment
* actually making up the class name is [3]. This value is saved in m_lastGoodRootIndex
* so that when we parse the next file name, we will try 3 right away. If 3 fails we
* will take the long approach. This is just a optimization cache value.
*/
private static int m_lastGoodRootIndex = -1;
/**
* Hide the constructor for utility class.
*/
private TestNGCommandLineArgs() {
// Hide constructor for utility class
}
/**
* Parses the command line options and returns a map from option string to parsed values.
* For example, if argv contains {..., "-sourcedir", "src/main", "-target", ...} then
* the map would contain an entry in which the key would be the "-sourcedir" String and
* the value would be the "src/main" String.
*
* @param argv the command line options.
* @return the parsed parameters as a map from option string to parsed values.
*/
public static Map parseCommandLine(final String[] originalArgv) {
// TODO CQ In this method, is this OK to simply ignore invalid parameters?
Map<String, Object> arguments = new HashMap<String, Object>();
String[] argv = expandArgv(originalArgv);
for (int i = 0; i < argv.length; i++) {
if (OUTDIR_COMMAND_OPT.equalsIgnoreCase(argv[i])) {
if ((i + 1) < argv.length) {
arguments.put(OUTDIR_COMMAND_OPT, argv[i + 1].trim());
}
else {
System.err.println("WARNING: missing output directory after -d. ignored");
}
i++;
}
else if (GROUPS_COMMAND_OPT.equalsIgnoreCase(argv[i])
|| EXCLUDED_GROUPS_COMMAND_OPT.equalsIgnoreCase(argv[i])) {
if ((i + 1) < argv.length) {
String option = null;
if (argv[i + 1].startsWith("\"")) {
if (argv[i + 1].endsWith("\"")) {
option = argv[i + 1].substring(1, argv[i + 1].length() - 1);
}
else {
System.err.println("WARNING: groups option is not well quoted:" + argv[i + 1]);
option = argv[i + 1].substring(1);
}
}
else {
option = argv[i + 1];
}
String opt = GROUPS_COMMAND_OPT.equalsIgnoreCase(argv[i])
? GROUPS_COMMAND_OPT : EXCLUDED_GROUPS_COMMAND_OPT;
arguments.put(opt, option);
}
else {
System.err.println("WARNING: missing groups parameter after -groups. ignored");
}
i++;
}
else if (LOG.equalsIgnoreCase(argv[i])) {
if ((i + 1) < argv.length) {
arguments.put(LOG, Integer.valueOf(argv[i + 1].trim()));
}
else {
System.err.println("WARNING: missing log level after -log. ignored");
}
i++;
}
else if (JUNIT_DEF_OPT.equalsIgnoreCase(argv[i])) {
arguments.put(JUNIT_DEF_OPT, Boolean.TRUE);
}
else if (TARGET_COMMAND_OPT.equalsIgnoreCase(argv[i])) {
if ((i + 1) < argv.length) {
if ("1.4".equals(argv[i + 1]) || "1.5".equals(argv[i + 1])) {
arguments.put(TARGET_COMMAND_OPT, argv[i + 1]);
}
else {
System.err.println(
"WARNING: missing/invalid target argument. Must be 1.4 or 1.5. Ignoring");
}
i++;
}
}
else if (TESTRUNNER_FACTORY_COMMAND_OPT.equalsIgnoreCase(argv[i])) {
if ((i + 1) < argv.length) {
arguments.put(TESTRUNNER_FACTORY_COMMAND_OPT, fileToClass(argv[++i]));
}
else {
System.err.println("WARNING: missing ITestRunnerFactory class or file argument after "
+ TESTRUNNER_FACTORY_COMMAND_OPT);
}
}
else if (LISTENER_COMMAND_OPT.equalsIgnoreCase(argv[i])) {
if ((i + 1) < argv.length) {
String[] strs = Utils.split(argv[++i], ";");
List<Class> classes = new ArrayList<Class>();
for (String cls : strs) {
classes.add(fileToClass(cls));
}
arguments.put(LISTENER_COMMAND_OPT, classes);
}
else {
System.err.println("WARNING: missing ITestListener class/file list argument after "
+ LISTENER_COMMAND_OPT);
}
}
else if (TESTCLASS_COMMAND_OPT.equalsIgnoreCase(argv[i])) {
if ((i + 1) < argv.length) {
while ((i + 1) < argv.length) {
String nextArg = argv[i + 1].trim();
if (!nextArg.toLowerCase().endsWith(".xml") && !nextArg.startsWith("-")) {
// Assume it's a class name
List<Class> l = (List<Class>) arguments.get(TESTCLASS_COMMAND_OPT);
if (null == l) {
l = new ArrayList<Class>();
arguments.put(TESTCLASS_COMMAND_OPT, l);
}
Class cls = fileToClass(nextArg);
if (null != cls) {
l.add(cls);
}
i++;
} // if
else {
break;
}
}
}
else {
TestNG.exitWithError("-testclass must be followed by a classname");
}
}
else if (TESTJAR_COMMAND_OPT.equalsIgnoreCase(argv[i])) {
if ((i + 1) < argv.length) {
arguments.put(TESTJAR_COMMAND_OPT, argv[i + 1].trim());
}
else {
TestNG.exitWithError("-testjar must be followed by a valid jar");
}
i++;
}
else if (SRC_COMMAND_OPT.equalsIgnoreCase(argv[i])) {
if ((i + 1) < argv.length) {
arguments.put(SRC_COMMAND_OPT, argv[i + 1].trim());
}
else {
TestNG.exitWithError(SRC_COMMAND_OPT + " must be followed by a directory path");
}
i++;
}
else if (HOST_COMMAND_OPT.equals(argv[i])) {
String hostAddress = "127.0.0.1";
if ((i + 1) < argv.length) {
hostAddress = argv[i + 1].trim();
i++;
}
else {
System.out.println("WARNING: "
+ HOST_COMMAND_OPT
+ " option should be followed by the host address. "
+ "Using default localhost.");
}
arguments.put(HOST_COMMAND_OPT, hostAddress);
}
else if (PORT_COMMAND_OPT.equals(argv[i])) {
String portNumber = null;
if ((i + 1) < argv.length) {
portNumber = argv[i + 1].trim();
}
else {
TestNG.exitWithError(
PORT_COMMAND_OPT + " option should be followed by a valid port number.");
}
arguments.put(PORT_COMMAND_OPT, portNumber);
i++;
}
else if (SLAVE_OPT.equals(argv[i])) {
String clientPortNumber = null;
if ((i + 1) < argv.length) {
clientPortNumber = argv[i + 1].trim();
}
else {
TestNG.exitWithError(SLAVE_OPT + " option should be followed by a valid port number.");
}
arguments.put(SLAVE_OPT, clientPortNumber);
i++;
}
else if (HOSTFILE_OPT.equals(argv[i])) {
String hostFile = null;
if ((i + 1) < argv.length) {
hostFile = argv[i + 1].trim();
}
else {
TestNG.exitWithError(HOSTFILE_OPT + " option should be followed by the name of a file.");
}
arguments.put(HOSTFILE_OPT, hostFile);
i++;
}
else if (THREAD_COUNT.equalsIgnoreCase(argv[i])) {
if ((i + 1) < argv.length) {
arguments.put(THREAD_COUNT, argv[i + 1]);
i++;
}
}
//
// Unknown option
//
else if (argv[i].startsWith("-")) {
TestNG.exitWithError("Unknown option: " + argv[i]);
}
else {
List<String> suiteDefs = new ArrayList<String>();
for (int k = i; k < argv.length; k++) {
String file = argv[k].trim();
if (file.toLowerCase().endsWith(".xml")) {
suiteDefs.add(file);
i++;
}
}
arguments.put(SUITE_DEF_OPT, suiteDefs);
}
}
return arguments;
}
/**
* Expand the command line parameters to take @ parameters into account.
* When @ is encountered, the content of the file that follows is inserted
* in the command line
*/
private static String[] expandArgv(String[] originalArgv) {
List<String> vResult = new ArrayList<String>();
for (String arg : originalArgv) {
if (arg.startsWith("@")) {
String fileName = arg.substring(1);
List<String> lines = readFile(fileName);
for (String line : lines) {
List<String> args = parseArgs(line);
for (String oneArg : args) {
vResult.add(oneArg);
}
}
}
else {
vResult.add(arg);
}
}
return vResult.toArray(new String[vResult.size()]);
}
/**
* Break a line of parameters into individual parameters.
* TODO(cbeust): Handled double quotes
*/
private static List<String> parseArgs(String line) {
List<String> result = new ArrayList<String>();
StringTokenizer st = new StringTokenizer(line);
while (st.hasMoreTokens()) {
result.add(st.nextToken());
}
return result;
}
public static List<String> readFile(String fileName) {
List<String> result = new ArrayList<String>();
try {
//
// Sets up a file reader to read the file passed on the command line one
// character at a time
//
FileReader input = new FileReader(fileName);
/*
* Filter FileReader through a Buffered read to read a line at a time
*/
BufferedReader bufRead = new BufferedReader(input);
String line; // String that holds current file line
// Read first line
line = bufRead.readLine();
// Read through file one line at time. Print line # and line
while (line != null) {
result.add(line);
line = bufRead.readLine();
}
bufRead.close();
}
catch (IOException e) {
e.printStackTrace();
}
return result;
}
/**
* Returns the Class object corresponding to the given name. The name may be
* of the following form:
* <ul>
* <li>A class name: "org.testng.TestNG"</li>
* <li>A class file name: "/testng/src/org/testng/TestNG.class"</li>
* <li>A class source name: "d:\testng\src\org\testng\TestNG.java"</li>
* </ul>
*
* @param file
* the class name.
* @return the class corresponding to the name specified.
*/
private static Class fileToClass(String file) {
Class result = null;
int classIndex = file.indexOf(".class");
if (-1 == classIndex) {
classIndex = file.indexOf(".java");
if (-1 == classIndex) {
// Doesn't end in .java or .class, assume it's a class name
result = ClassHelper.forName(file);
if (null == result) {
throw new TestNGException("Cannot load class from file: " + file);
}
return result;
}
}
// Transforms the file name into a class name.
// Remove the ".class" or ".java" extension.
String shortFileName = file.substring(0, classIndex);
// Split file name into segments. For example "c:/java/classes/com/foo/A"
// becomes {"c:", "java", "classes", "com", "foo", "A"}
String[] segments = shortFileName.split("[/\\\\]", -1);
//
// Check if the last good root index works for this one. For example, if the previous
// name was "c:/java/classes/com/foo/A.class" then m_lastGoodRootIndex is 3 and we
// try to make a class name ignoring the first m_lastGoodRootIndex segments (3). This
// will succeed rapidly if the path is the same as the one from the previous name.
//
if (-1 != m_lastGoodRootIndex) {
// TODO use a SringBuffer here
String className = segments[m_lastGoodRootIndex];
for (int i = m_lastGoodRootIndex + 1; i < segments.length; i++) {
className += "." + segments[i];
}
result = ClassHelper.forName(className);
if (null != result) {
return result;
}
}
//
// We haven't found a good root yet, start by resolving the class from the end segment
// and work our way up. For example, if we start with "c:/java/classes/com/foo/A"
// we'll start by resolving "A", then "foo.A", then "com.foo.A" until something
// resolves. When it does, we remember the path we are at as "lastGoodRoodIndex".
//
// TODO CQ use a StringBuffer here
String className = null;
for (int i = segments.length - 1; i >= 0; i--) {
if (null == className) {
className = segments[i];
}
else {
className = segments[i] + "." + className;
}
result = ClassHelper.forName(className);
if (null != result) {
m_lastGoodRootIndex = i;
break;
}
}
if (null == result) {
throw new TestNGException("Cannot load class from file: " + file);
}
return result;
}
// private static void ppp(Object msg) {
// System.out.println("[CMD]: " + msg);
// }
/**
* Prints the usage message to System.out. This message describes all the command line
* options.
*/
public static void usage() {
System.out.println("Usage:");
System.out.println("[" + OUTDIR_COMMAND_OPT + " output-directory]");
System.out.println("\t\tdefault output directory to : " + "test-output");
System.out.println("[" + TESTCLASS_COMMAND_OPT
+ " list of .class files or list of class names]");
System.out.println("[" + SRC_COMMAND_OPT + " a source directory]");
System.out.println("[" + TARGET_COMMAND_OPT + " 1.4 or 1.5]");
System.out.println("\t\tused only with JDK1.5 to specify the "
+ "annotation type used in test classes; default target: 1.5");
System.out.println("[" + GROUPS_COMMAND_OPT + " comma-separated list of group names to be run]");
System.out.println("\t\tworks only with " + TESTCLASS_COMMAND_OPT);
System.out.println("[" + EXCLUDED_GROUPS_COMMAND_OPT
+ " comma-separated list of group names to be excluded]");
System.out.println("\t\tworks only with " + TESTCLASS_COMMAND_OPT);
System.out.println("[" + TESTRUNNER_FACTORY_COMMAND_OPT
+ " list of .class files or list of class names implementing "
+ ITestRunnerFactory.class.getName()
+ "]");
System.out.println("[" + LISTENER_COMMAND_OPT
+ " list of .class files or list of class names implementing "
+ ITestListener.class.getName()
+ " and/or "
+ ISuiteListener.class.getName()
+ "]");
System.out.println("[" + THREAD_COUNT
+ " number of threads to use"
+ "]");
System.out.println("[suite definition files*]");
System.out.println("");
System.out.println("For details please consult documentation.");
}
}