NIHVIVO-222 Second step at coding the Java-based SeleniumRunner.

This commit is contained in:
jeb228 2010-05-17 14:17:01 +00:00
parent 1c5e7fdde8
commit 92f9280af0
11 changed files with 908 additions and 176 deletions

View file

@ -34,12 +34,12 @@ public class CommandRunner {
private File workingDirectory;
/* Gets informed of output as it arrives. Never null. */
private final Logger logger;
private final Listener listener;
private final Map<String, String> environmentAdditions = new HashMap<String, String>();
public CommandRunner(SeleniumRunnerParameters parms) {
this.logger = parms.getLogger();
this.listener = parms.getListener();
}
/** Set the directory that the command will run in. */
@ -61,7 +61,7 @@ public class CommandRunner {
* {@link java.lang.ProcessBuilder#ProcessBuilder(List)}.
*/
public void run(List<String> command) throws CommandRunnerException {
logger.subProcessStart(command);
listener.subProcessStart(command);
try {
ProcessBuilder builder = new ProcessBuilder(command);
@ -93,7 +93,43 @@ public class CommandRunner {
throw new CommandRunnerException(
"Exception when handling sub-process:", e);
}
logger.subProcessStop(command);
listener.subProcessStop(command);
}
/**
* Run the command and don't wait for it to complete. {@link #stdErr} and
* {@link #stdOut} will not be set, but output from the process may be sent
* to the listener at any time.
*
* @param command
* a list containing the operating system program and its
* arguments. See
* {@link java.lang.ProcessBuilder#ProcessBuilder(List)}.
*/
public void runAsBackground(List<String> command)
throws CommandRunnerException {
listener.subProcessStartInBackground(command);
try {
ProcessBuilder builder = new ProcessBuilder(command);
if (workingDirectory != null) {
builder.directory(workingDirectory);
}
if (!environmentAdditions.isEmpty()) {
builder.environment().putAll(this.environmentAdditions);
}
Process process = builder.start();
StreamEater outputEater = new StreamEater(process.getInputStream(),
false);
StreamEater errorEater = new StreamEater(process.getErrorStream(),
true);
} catch (IOException e) {
throw new CommandRunnerException(
"Exception when handling sub-process:", e);
}
}
public int getReturnCode() {
@ -136,11 +172,11 @@ public class CommandRunner {
if (howMany > 0) {
String string = new String(buffer, 0, howMany);
contents.write(string);
if (isError) {
logger.subProcessErrout(string);
listener.subProcessErrout(string);
} else {
logger.subProcessStdout(string);
listener.subProcessStdout(string);
}
} else if (howMany == 0) {
Thread.yield();

View file

@ -0,0 +1,26 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.testrunner;
/**
* Indicates a problem so severe that we might as well stop now.
*/
public class FatalException extends RuntimeException {
public FatalException() {
super();
}
public FatalException(String message) {
super(message);
}
public FatalException(Throwable cause) {
super(cause);
}
public FatalException(String message, Throwable cause) {
super(message, cause);
}
}

View file

@ -0,0 +1,209 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.testrunner;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
/**
* A listener for all events that occur during the run. In this basic
* implementation, each event is simply formatted and written to a log file or
* {@link PrintStream}.
*/
public class Listener {
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss.SSS");
private final Writer writer;
// ----------------------------------------------------------------------
// Listener methods
// ----------------------------------------------------------------------
public Listener(PrintStream out) {
this.writer = new OutputStreamWriter(out);
}
public Listener(File logFile) throws IOException {
this.writer = new FileWriter(logFile);
}
// ----------------------------------------------------------------------
// Listener methods
// ----------------------------------------------------------------------
public void runStarted() {
log("Run started.");
}
public void runFailed(Exception e) {
log("Run failed - fatal error");
log(e);
}
public void runStopped() {
log("Run stopped.");
}
public void webappStopping(String tomcatStopCommand) {
log("Stopping tomcat: " + tomcatStopCommand);
}
public void webappStopFailed(int returnCode) {
log("Failed to stop tomcat; return code was " + returnCode);
}
public void webappWaitingForStop(int tomcatStopDelay) {
log("Waiting " + tomcatStopDelay + " seconds for tomcat to stop.");
}
public void webappStopped() {
log("Tomcat stopped.");
}
public void dropDatabaseStarting(String statement) {
log("Dropping database: " + statement);
}
public void dropDatabaseFailed(int returnCode) {
log("Failed to drop the database; return code was " + returnCode);
}
public void dropDatabaseComplete() {
log("Dropped database.");
}
public void loadDatabaseStarting(String statement) {
log("Loading the database: " + statement);
}
public void loadDatabaseFailed(int returnCode) {
log("Failed to load the database; return code was " + returnCode);
}
public void loadDatabaseComplete() {
log("Loaded the database.");
}
public void webappStarting(String tomcatStartCommand) {
log("Starting tomcat: " + tomcatStartCommand);
}
public void webappStartFailed(int returnCode) {
log("Failed to start tomcat; return code was " + returnCode);
}
public void webappWaitingForStart(int tomcatStartDelay) {
log("Waiting " + tomcatStartDelay + " seconds for tomcat to start.");
}
public void webappStarted() {
log("Tomcat started.");
}
public void subProcessStart(List<String> command) {
log("Subprocess started: " + command);
}
public void subProcessStartInBackground(List<String> command) {
log("Subprocess started in background: " + command);
}
public void subProcessStdout(String string) {
logRawText(string);
}
public void subProcessErrout(String string) {
logRawText(string);
}
public void subProcessStop(List<String> command) {
log("Subprocess stopped: " + command);
}
public void suiteStarted(File suiteDir) {
log("Suite started: " + suiteDir.getName());
}
public void suiteTestingStarted(File suiteDir) {
log("Suite testing started: " + suiteDir.getName());
}
public void suiteFailed(File suiteDir, int returnCode) {
log("Suite failed: " + suiteDir.getName() + ", returnCode="
+ returnCode);
}
public void suiteFailed(File suiteDir, Exception e) {
log("Suite failed: " + suiteDir.getName());
log(e);
}
public void suiteTestingStopped(File suiteDir) {
log("Suite testing stopped: " + suiteDir.getName());
}
public void suiteStopped(File suiteDir) {
log("Suite stopped: " + suiteDir.getName());
}
public void cleanUploadStart(File uploadDirectory) {
log("Upload cleaning started: " + uploadDirectory.getPath());
}
public void cleanUploadFailed(File uploadDirectory, IOException e) {
log("Upload cleaning failed: " + uploadDirectory.getPath());
log(e);
}
public void cleanUploadStop(File uploadDirectory) {
log("Upload cleaning stopped: " + uploadDirectory.getPath());
}
// ----------------------------------------------------------------------
// Helper methods
// ----------------------------------------------------------------------
private void logRawText(String rawText) {
try {
writer.write(rawText);
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
private void log(String message) {
try {
writer.write(timeStamp() + " " + message + "\n");
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
private void log(Throwable t) {
try {
t.printStackTrace(new PrintWriter(writer));
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Convert the current date and time to a string for the log.
*/
private String timeStamp() {
return DATE_FORMAT.format(new Date());
}
}

View file

@ -1,117 +0,0 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.testrunner;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
/**
* TODO
*/
public class Logger {
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss.SSS");
private final Writer writer;
public Logger(PrintStream out) {
this.writer = new OutputStreamWriter(out);
}
public Logger(File logFile) throws IOException {
this.writer = new FileWriter(logFile);
}
public void runStarted() {
log("Run started.");
}
public void runStopped() {
log("Run stopped.");
}
public void subProcessStart(List<String> command) {
log("Subprocess started: " + command);
}
public void subProcessStdout(String string) {
logRawText(string);
}
public void subProcessErrout(String string) {
logRawText(string);
}
public void subProcessStop(List<String> command) {
log("Subprocess stopped: " + command);
}
public void suiteStarted(File suiteDir) {
log("Suite started: " + suiteDir.getName());
}
public void suiteFailed(File suiteDir, IOException e) {
log("Suite failed: " + suiteDir.getName());
log(e);
}
public void suiteStopped(File suiteDir) {
log("Suite stopped: " + suiteDir.getName());
}
public void cleanUploadStart(File uploadDirectory) {
log("Upload cleaning started: " + uploadDirectory.getPath());
}
public void cleanUploadFailed(File uploadDirectory, IOException e) {
log("Upload cleaning failed: " + uploadDirectory.getPath());
log(e);
}
public void cleanUploadStop(File uploadDirectory) {
log("Upload cleaning stopped: " + uploadDirectory.getPath());
}
private void logRawText(String rawText) {
try {
writer.write(rawText);
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
private void log(String message) {
try {
writer.write(timeStamp() + " " + message + "\n");
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
private void log(Throwable t) {
try {
t.printStackTrace(new PrintWriter(writer));
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Convert the current date and time to a string for the log.
*/
private String timeStamp() {
return DATE_FORMAT.format(new Date());
}
}

View file

@ -2,25 +2,215 @@
package edu.cornell.mannlib.vitro.utilities.testrunner;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* TODO
* Resets the RDF-Model to a known state, in preparation for the next Selenium
* test suite.
*/
public class ModelCleaner {
private final ModelCleanerProperties properties;
private final CommandRunner runner;
private final Listener listener;
/**
* @param parms
*/
public ModelCleaner(SeleniumRunnerParameters parms) {
// TODO Auto-generated constructor stub
throw new RuntimeException("ModelCleaner Constructor not implemented.");
this.properties = parms.getModelCleanerProperties();
this.listener = parms.getListener();
this.runner = new CommandRunner(parms);
sanityCheck();
}
private void sanityCheck() {
executeMysqlStatement("show databases;");
int returnCode = runner.getReturnCode();
if (returnCode != 0) {
throw new FatalException(
"sanityCheck: Failed to execute a MySQL statement: "
+ "return code=" + returnCode);
}
}
/**
* Reset the RDF-Model to a known state, according to the parameters in the
* properties file.
*
* @throws CommandRunnerException
* if a problem occurs in a sub-process.
*/
public void clean() {
// TODO Auto-generated method stub
throw new RuntimeException("ModelCleaner.clean() not implemented.");
public void clean() throws CommandRunnerException {
stopTheWebapp();
dropDatabase();
createAndLoadDatabase();
startTheWebapp();
}
/**
* Stop Tomcat and wait the prescribed number of seconds for it to clean up.
*/
private void stopTheWebapp() throws CommandRunnerException {
String tomcatStopCommand = properties.getTomcatStopCommand();
int tomcatStopDelay = properties.getTomcatStopDelay();
listener.webappStopping(tomcatStopCommand);
runner.run(parseCommandLine(tomcatStopCommand));
int returnCode = runner.getReturnCode();
if (returnCode != 0) {
listener.webappStopFailed(returnCode);
// Throw no exception - this can happen if Tomcat isn't running.
}
listener.webappWaitingForStop(tomcatStopDelay);
try {
Thread.sleep(tomcatStopDelay * 1000L);
} catch (InterruptedException e) {
// Just continue.
}
listener.webappStopped();
}
/**
* Delete the database.
*/
private void dropDatabase() {
String mysqlStatement = "drop database " + properties.getMysqlDbName()
+ "; create database " + properties.getMysqlDbName()
+ " character set utf8;";
listener.dropDatabaseStarting(mysqlStatement);
executeMysqlStatement(mysqlStatement);
int returnCode = runner.getReturnCode();
if (returnCode != 0) {
listener.dropDatabaseFailed(returnCode);
throw new FatalException("dropDatabase() failed: return code="
+ returnCode);
}
listener.dropDatabaseComplete();
}
/**
* Rebuild the database.
*/
private void createAndLoadDatabase() {
String mysqlStatement = "source "
+ convertBackslashes(properties.getMysqlDumpfile()) + ";";
listener.loadDatabaseStarting(mysqlStatement);
executeMysqlStatement(mysqlStatement);
int returnCode = runner.getReturnCode();
if (returnCode != 0) {
listener.loadDatabaseFailed(returnCode);
throw new FatalException("loadDatabase() failed: return code="
+ returnCode);
}
listener.loadDatabaseComplete();
}
/**
* Start Tomcat and wait for it to initialize.
*/
private void startTheWebapp() {
String tomcatStartCommand = properties.getTomcatStartCommand();
int tomcatStartDelay = properties.getTomcatStartDelay();
listener.webappStarting(tomcatStartCommand);
try {
runner.runAsBackground(parseCommandLine(tomcatStartCommand));
} catch (CommandRunnerException e) {
throw new FatalException(e);
}
int returnCode = runner.getReturnCode();
if (returnCode != 0) {
listener.webappStartFailed(returnCode);
throw new FatalException("startTheWebapp() failed: return code="
+ returnCode);
}
listener.webappWaitingForStart(tomcatStartDelay);
try {
Thread.sleep(tomcatStartDelay * 1000L);
} catch (InterruptedException e) {
// Just continue.
}
listener.webappStarted();
}
/**
* Tell MySQL to execute this statement. If it fails, throw a fatal
* exception.
*/
private void executeMysqlStatement(String mysqlStatement) {
List<String> cmd = new ArrayList<String>();
cmd.add("mysql");
cmd.add("--user=" + properties.getMysqlUsername());
cmd.add("--password=" + properties.getMysqlPassword());
cmd.add("--database=" + properties.getMysqlDbName());
cmd.add("--execute=" + mysqlStatement);
try {
runner.run(cmd);
} catch (CommandRunnerException e) {
throw new FatalException(e);
}
}
/**
* A command line must be broken into separate arguments, where arguments
* are delimited by blanks unless the blank (and the argument) is enclosed
* in quotes.
*/
static List<String> parseCommandLine(String commandLine) {
List<String> pieces = new ArrayList<String>();
StringBuilder piece = null;
boolean inDelimiter = true;
boolean inQuotes = false;
for (int i = 0; i < commandLine.length(); i++) {
char thisChar = commandLine.charAt(i);
if ((thisChar == ' ') && !inQuotes) {
if (inDelimiter) {
// No effect.
} else {
inDelimiter = true;
pieces.add(piece.toString());
}
} else if (thisChar == '"') {
// Quotes are not carried into the parsed strings.
inQuotes = !inQuotes;
} else { // Not a blank or a quote.
if (inDelimiter) {
inDelimiter = false;
piece = new StringBuilder();
}
piece.append(thisChar);
}
}
// There is an implied delimiter at the end of the command line.
if (!inDelimiter) {
pieces.add(piece.toString());
}
// Quotes must appear in pairs
if (inQuotes) {
throw new IllegalArgumentException(
"Command line contains mismatched quotes: " + commandLine);
}
return pieces;
}
static String convertBackslashes(File file) {
return file.getPath().replace("\\", "/");
}
}

View file

@ -0,0 +1,138 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.testrunner;
import java.io.File;
import java.util.Properties;
/**
* Hold the runtime properties that pertain specifically to cleaning the data
* model.
*/
public class ModelCleanerProperties {
public static final String PROP_TOMCAT_START_COMMAND = "tomcat_start_command";
public static final String PROP_TOMCAT_START_DELAY = "tomcat_start_delay";
public static final String PROP_TOMCAT_STOP_COMMAND = "tomcat_stop_command";
public static final String PROP_TOMCAT_STOP_DELAY = "tomcat_stop_delay";
public static final String PROP_MYSQL_USERNAME = "mysql_username";
public static final String PROP_MYSQL_PASSWORD = "mysql_password";
public static final String PROP_MYSQL_DB_NAME = "mysql_db_name";
public static final String PROP_MYSQL_DUMPFILE = "mysql_dumpfile";
private final String tomcatStartCommand;
private final int tomcatStartDelay;
private final String tomcatStopCommand;
private final int tomcatStopDelay;
private final String mysqlUsername;
private final String mysqlPassword;
private final String mysqlDbName;
private final File mysqlDumpfile;
/**
* Confirm that we have the expected properties, and that their values seem
* reasonable.
*/
public ModelCleanerProperties(Properties props) {
this.tomcatStartCommand = getRequiredProperty(props,
PROP_TOMCAT_START_COMMAND);
this.tomcatStartDelay = getRequiredIntegerProperty(props,
PROP_TOMCAT_START_DELAY);
this.tomcatStopCommand = getRequiredProperty(props,
PROP_TOMCAT_STOP_COMMAND);
this.tomcatStopDelay = getRequiredIntegerProperty(props,
PROP_TOMCAT_STOP_DELAY);
this.mysqlUsername = getRequiredProperty(props, PROP_MYSQL_USERNAME);
this.mysqlPassword = getRequiredProperty(props, PROP_MYSQL_PASSWORD);
this.mysqlDbName = getRequiredProperty(props, PROP_MYSQL_DB_NAME);
this.mysqlDumpfile = confirmDumpfile(props);
}
public String getTomcatStartCommand() {
return tomcatStartCommand;
}
public int getTomcatStartDelay() {
return tomcatStartDelay;
}
public String getTomcatStopCommand() {
return tomcatStopCommand;
}
public int getTomcatStopDelay() {
return tomcatStopDelay;
}
public String getMysqlUsername() {
return mysqlUsername;
}
public String getMysqlPassword() {
return mysqlPassword;
}
public String getMysqlDbName() {
return mysqlDbName;
}
public File getMysqlDumpfile() {
return mysqlDumpfile;
}
/**
* Get the value for this property. If there isn't one, or if it's empty,
* complain.
*/
private String getRequiredProperty(Properties props, String key) {
String value = props.getProperty(key);
if ((value == null) || (value.trim().length() == 0)) {
throw new IllegalArgumentException(
"Property file must provide a value for '" + key + "'");
}
return value;
}
private int getRequiredIntegerProperty(Properties props, String key) {
String value = getRequiredProperty(props, key);
try {
return Integer.parseInt(value.trim());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Property value for '" + key
+ "' is not a valid integer: " + value);
}
}
/**
* The dumpfile parameter must point to an existing file.
*/
private File confirmDumpfile(Properties props) {
String filename = getRequiredProperty(props, PROP_MYSQL_DUMPFILE);
File dumpfile = new File(filename);
if (!dumpfile.exists()) {
throw new IllegalArgumentException("Invalid value for '"
+ PROP_MYSQL_DUMPFILE + "': file '" + filename
+ "' does not exist.");
}
if (!dumpfile.isFile()) {
throw new IllegalArgumentException("Invalid value for '"
+ PROP_MYSQL_DUMPFILE + "': '" + filename
+ "' is not a file.");
}
if (!dumpfile.canRead()) {
throw new IllegalArgumentException("Invalid value for '"
+ PROP_MYSQL_DUMPFILE + "': file '" + filename
+ "' is not readable.");
}
return dumpfile;
}
public String toString() {
return "\n tomcatStartCommand: " + tomcatStartCommand
+ "\n tomcatStartDelay: " + tomcatStartDelay
+ "\n tomcatStopCommand: " + tomcatStopCommand
+ "\n tomcatStopDelay: " + tomcatStopDelay;
}
}

View file

@ -2,6 +2,8 @@
package edu.cornell.mannlib.vitro.utilities.testrunner;
import static edu.cornell.mannlib.vitro.utilities.testrunner.SeleniumRunnerParameters.LOGFILE_NAME;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
@ -13,23 +15,23 @@ import java.util.List;
*/
public class SeleniumRunner {
private final SeleniumRunnerParameters parms;
private final Logger logger;
private final Listener listener;
private final UploadAreaCleaner uploadCleaner;
private final ModelCleaner modelCleaner;
private final SuiteRunner suiteRunner;
public SeleniumRunner(SeleniumRunnerParameters parms) {
this.parms = parms;
this.logger = parms.getLogger();
this.listener = parms.getListener();
this.uploadCleaner = new UploadAreaCleaner(parms);
this.modelCleaner = new ModelCleaner(parms);
this.suiteRunner = new SuiteRunner(parms);
}
public void runSelectedSuites() {
logger.runStarted();
listener.runStarted();
for (File suiteDir : parms.getSelectedSuites()) {
logger.suiteStarted(suiteDir);
listener.suiteStarted(suiteDir);
try {
if (parms.isCleanModel()) {
modelCleaner.clean();
@ -39,11 +41,17 @@ public class SeleniumRunner {
}
suiteRunner.runSuite(suiteDir);
} catch (IOException e) {
logger.suiteFailed(suiteDir, e);
listener.suiteFailed(suiteDir, e);
} catch (CommandRunnerException e) {
listener.suiteFailed(suiteDir, e);
} catch (FatalException e) {
listener.runFailed(e);
e.printStackTrace();
break;
}
logger.suiteStopped(suiteDir);
listener.suiteStopped(suiteDir);
}
logger.runStopped();
listener.runStopped();
}
private static void selectAllSuites(SeleniumRunnerParameters parms) {
@ -54,9 +62,13 @@ public class SeleniumRunner {
parms.setSelectedSuites(suites);
}
/**
* @param args
*/
private static void usage(String message) {
System.out.println(message);
System.out.println("Usage is: SeleniumRunner <parameters_file> "
+ "[\"interactive\"]");
System.exit(1);
}
public static void main(String[] args) {
SeleniumRunnerParameters parms = null;
boolean interactive = false;
@ -82,20 +94,20 @@ public class SeleniumRunner {
// TODO hook up the GUI.
throw new RuntimeException("interactive mode not implemented.");
} else {
File logFile = new File(parms.getOutputDirectory(), LOGFILE_NAME);
System.out.println("Log file is '" + logFile.getPath() + "'");
// Run all of the suites.
// For each suite, clean the model and the upload area.
selectAllSuites(parms);
parms.setCleanModel(true);
parms.setCleanUploads(true);
System.out.println(parms);
SeleniumRunner runner = new SeleniumRunner(parms);
runner.runSelectedSuites();
}
}
private static void usage(String message) {
System.out.println(message);
System.out.println("Usage is: SeleniumRunner <parameters_file> "
+ "[\"interactive\"]");
System.exit(1);
}
}

View file

@ -19,22 +19,32 @@ import java.util.Properties;
* with modifications from the GUI if we are running interactively.
*/
public class SeleniumRunnerParameters {
private static final String PROP_OUTPUT_DIRECTORY = "output_directory";
private static final String PROP_UPLOAD_DIRECTORY = "upload_directory";
private static final String PROP_SUITE_DIRECTORIES = "suite_parent_directories";
public static final String PROP_OUTPUT_DIRECTORY = "output_directory";
public static final String PROP_UPLOAD_DIRECTORY = "upload_directory";
public static final String PROP_SUITE_DIRECTORIES = "suite_parent_directories";
public static final String PROP_WEBSITE_URL = "website_url";
public static final String PROP_USER_EXTENSIONS_PATH = "user_extensions_path";
public static final String PROP_FIREFOX_PROFILE_PATH = "firefox_profile_template_path";
public static final String PROP_SUITE_TIMEOUT_LIMIT = "suite_timeout_limit";
public static final String PROP_SELENIUM_JAR_PATH = "selenium_jar_path";
private static final String LOGFILE_NAME = "log_file.txt";
public static final String LOGFILE_NAME = "log_file.txt";
private final String websiteUrl;
private final File userExtensionsFile;
private final File firefoxProfileDir;
private final int suiteTimeoutLimit;
private final File seleniumJarPath;
private final File uploadDirectory;
private final File outputDirectory;
private final File logFile;
private final Collection<File> suiteParentDirectories;
private final ModelCleanerProperties modelCleanerProperties;
private Collection<File> selectedSuites = Collections.emptySet();
private boolean cleanModel = true;
private boolean cleanUploads = true;
private Logger logger = new Logger(System.out);
private Listener listener = new Listener(System.out);
/**
* Read the required properties from the property file, and do some checks
@ -48,15 +58,26 @@ public class SeleniumRunnerParameters {
Properties props = new Properties();
props.load(propsReader);
this.websiteUrl = getRequiredProperty(props, PROP_WEBSITE_URL);
this.userExtensionsFile = checkReadableFile(props,
PROP_USER_EXTENSIONS_PATH);
this.firefoxProfileDir = checkOptionalReadableDirectory(props,
PROP_FIREFOX_PROFILE_PATH);
this.suiteTimeoutLimit = getRequiredIntegerProperty(props,
PROP_SUITE_TIMEOUT_LIMIT);
this.seleniumJarPath = checkReadableFile(props,
PROP_SELENIUM_JAR_PATH);
this.uploadDirectory = checkReadWriteDirectory(props,
PROP_UPLOAD_DIRECTORY);
this.outputDirectory = checkReadWriteDirectory(props,
PROP_OUTPUT_DIRECTORY);
this.logFile = new File(this.outputDirectory, LOGFILE_NAME);
this.logger = new Logger(this.logFile);
this.listener = new Listener(this.logFile);
this.suiteParentDirectories = checkSuiteParentDirectories(props);
this.modelCleanerProperties = new ModelCleanerProperties(props);
} finally {
if (propsReader != null) {
try {
@ -68,6 +89,36 @@ public class SeleniumRunnerParameters {
}
}
/**
* If there is a parameter for this key, it should point to a readable
* directory.
*/
private File checkOptionalReadableDirectory(Properties props, String key) {
String value = props.getProperty(key);
if (value == null) {
return null;
}
File dir = new File(value);
if (!dir.exists()) {
throw new IllegalArgumentException("Directory " + key + " '"
+ value + "' does not exist.");
}
if (!dir.isDirectory()) {
throw new IllegalArgumentException("Directory " + key + " '"
+ value + "' is not a directory.");
}
if (!dir.canRead()) {
throw new IllegalArgumentException("Directory " + key + " '"
+ value + "' is not readable.");
}
return dir;
}
/**
* Check that there is a property for the output directory, and that it
* points to a valid directory.
@ -99,6 +150,26 @@ public class SeleniumRunnerParameters {
return dir;
}
private File checkReadableFile(Properties props, String key) {
String value = getRequiredProperty(props, key);
File file = new File(value);
if (!file.exists()) {
throw new IllegalArgumentException("File " + key
+ ": '' does not exist.");
}
if (!file.isFile()) {
throw new IllegalArgumentException("File " + key
+ ": '' is not a file.");
}
if (!file.canRead()) {
throw new IllegalArgumentException("File " + key
+ ": '' is not readable.");
}
return file;
}
/**
* Get the property for the suite directories and ensure that each one is
* indeed a readable directory.
@ -123,6 +194,7 @@ public class SeleniumRunnerParameters {
throw new IllegalArgumentException("Suite directory '"
+ dir.getPath() + "' is not readable.");
}
dirs.add(dir);
}
return dirs;
}
@ -140,22 +212,67 @@ public class SeleniumRunnerParameters {
return value;
}
public Logger getLogger() {
return logger;
/**
* This required property must be a valid integer.
*/
private int getRequiredIntegerProperty(Properties props, String key) {
String value = getRequiredProperty(props, key);
try {
return Integer.parseInt(value.trim());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Property value for '" + key
+ "' is not a valid integer: " + value);
}
}
public void setLogger(Logger logger) {
this.logger = logger;
public String getWebsiteUrl() {
return websiteUrl;
}
public File getUserExtensionsFile() {
return userExtensionsFile;
}
public boolean hasFirefoxProfileDir() {
return firefoxProfileDir != null;
}
public File getFirefoxProfileDir() {
return firefoxProfileDir;
}
public int getSuiteTimeoutLimit() {
return suiteTimeoutLimit;
}
public File getSeleniumJarPath() {
return seleniumJarPath;
}
public Listener getListener() {
return listener;
}
public void setListener(Listener logger) {
this.listener = logger;
}
public File getUploadDirectory() {
return uploadDirectory;
}
public File getOutputDirectory() {
return outputDirectory;
}
public Collection<File> getSuiteParentDirectories() {
return suiteParentDirectories;
}
public ModelCleanerProperties getModelCleanerProperties() {
return modelCleanerProperties;
}
public void setSelectedSuites(Collection<File> selectedSuites) {
this.selectedSuites = selectedSuites;
}
@ -180,22 +297,50 @@ public class SeleniumRunnerParameters {
this.cleanUploads = cleanUploads;
}
public String toString() {
return "Parameters:" + "\n websiteUrl: " + websiteUrl
+ "\n userExtensionsFile: " + userExtensionsFile.getPath()
+ "\n firefoxProfileDir: " + firefoxProfileDir.getPath()
+ "\n suiteTimeoutLimit: " + suiteTimeoutLimit
+ "\n seleniumJarPath: " + seleniumJarPath.getPath()
+ "\n uploadDirectory: " + uploadDirectory.getPath()
+ "\n outputDirectory: " + outputDirectory.getPath()
+ "\n suiteParentDirectories: " + suiteParentDirectories
+ "\n modelCleanerProperties: " + modelCleanerProperties
+ "\n\n selectedSuites: " + showSelectedSuites()
+ "\n cleanModel: " + cleanModel + "\n cleanUploads: "
+ cleanUploads;
}
private String showSelectedSuites() {
StringBuilder buffer = new StringBuilder();
for (File suite : selectedSuites) {
buffer.append("\n ").append(suite.getPath());
}
return buffer.toString();
}
/**
* Look inside this parent directory and find any suite directories. You can
* recognize a suite directory because it contains a file named Suite.html.
*/
public Collection<File> findSuiteDirs(File parentDir) {
System.out.println("parentDir: " + parentDir);
return Arrays.asList(parentDir.listFiles(new FileFilter() {
public boolean accept(File pathname) {
if (!pathname.isDirectory()) {
return false;
}
if (pathname.getName().charAt(0) == '.') {
return false;
}
File suiteFile = new File(pathname, "Suite.html");
if (suiteFile.exists()) {
return true;
} else {
logger.subProcessErrout("Warning: suite file '" + suiteFile.getPath()
+ "' does not exist.\n");
listener.subProcessErrout("Warning: suite file '"
+ suiteFile.getPath() + "' does not exist.\n");
return false;
}
}

View file

@ -3,26 +3,68 @@
package edu.cornell.mannlib.vitro.utilities.testrunner;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* TODO
* Run a Selenium TestSuite in a sub-process.
*/
public class SuiteRunner {
/**
* @param parms
*/
private final SeleniumRunnerParameters parms;
private final CommandRunner runner;
private final Listener listener;
public SuiteRunner(SeleniumRunnerParameters parms) {
// TODO Auto-generated constructor stub
throw new RuntimeException("SuiteRunner Constructor not implemented.");
this.parms = parms;
this.runner = new CommandRunner(parms);
this.listener = parms.getListener();
}
/**
* @param suiteDir
* Run the suite.
*/
public void runSuite(File suiteDir) {
// TODO Auto-generated method stub
throw new RuntimeException("SuiteRunner.runSuite() not implemented.");
listener.suiteTestingStarted(suiteDir);
List<String> cmd = new ArrayList<String>();
cmd.add("java");
cmd.add("-jar");
cmd.add(parms.getSeleniumJarPath().getPath());
cmd.add("-singleWindow");
cmd.add("-timeout");
cmd.add(String.valueOf(parms.getSuiteTimeoutLimit()));
cmd.add("-userExtensions");
cmd.add(parms.getUserExtensionsFile().getPath());
if (parms.hasFirefoxProfileDir()) {
cmd.add("-firefoxProfileTemplate");
cmd.add(parms.getFirefoxProfileDir().getPath());
}
String suiteName = suiteDir.getName();
File outputFile = new File(parms.getOutputDirectory(), suiteName
+ ".html");
File suiteFile = new File(suiteDir, "Suite.html");
cmd.add("-htmlSuite");
cmd.add("*firefox");
cmd.add(parms.getWebsiteUrl());
cmd.add(suiteFile.getPath());
cmd.add(outputFile.getPath());
try {
runner.run(cmd);
} catch (CommandRunnerException e) {
throw new FatalException(e);
}
int returnCode = runner.getReturnCode();
if (returnCode != 0) {
listener.suiteFailed(suiteDir, returnCode);
}
listener.suiteTestingStopped(suiteDir);
}
}

View file

@ -11,11 +11,11 @@ import java.io.IOException;
*/
public class UploadAreaCleaner {
private final SeleniumRunnerParameters parms;
private final Logger logger;
private final Listener listener;
public UploadAreaCleaner(SeleniumRunnerParameters parms) {
this.parms = parms;
this.logger = parms.getLogger();
this.listener = parms.getListener();
}
/**
@ -29,7 +29,7 @@ public class UploadAreaCleaner {
+ "' is not a directory.");
}
logger.cleanUploadStart(uploadDirectory);
listener.cleanUploadStart(uploadDirectory);
try {
for (File file : uploadDirectory.listFiles()) {
@ -40,10 +40,10 @@ public class UploadAreaCleaner {
}
}
} catch (IOException e) {
logger.cleanUploadFailed(uploadDirectory, e);
listener.cleanUploadFailed(uploadDirectory, e);
throw e;
} finally {
logger.cleanUploadStop(uploadDirectory);
listener.cleanUploadStop(uploadDirectory);
}
}

View file

@ -0,0 +1,51 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.testrunner;
import static org.junit.Assert.*;
import java.util.Arrays;
import org.junit.Test;
/**
* TODO
*/
public class ModelCleanerTest {
// ----------------------------------------------------------------------
// Tests for parseCommandLine()
// ----------------------------------------------------------------------
@Test
public void oneArgument() {
assertExpectedParsing("oneArgument", "oneArgument");
}
@Test
public void multipleArguments() {
assertExpectedParsing("more than one", "more", "than", "one");
}
@Test
public void quotedArgument() {
assertExpectedParsing("contains \"quoted blank\" string", "contains",
"quoted blank", "string");
}
@Test(expected = IllegalArgumentException.class)
public void mismatchedQuotes() {
assertExpectedParsing("contains mismatched \"quote");
}
@Test
public void emptyLine() {
assertExpectedParsing("");
}
private void assertExpectedParsing(String commandLine,
String... expectedPieces) {
assertEquals("parse", Arrays.asList(expectedPieces), ModelCleaner
.parseCommandLine(commandLine));
}
}