diff --git a/utilities/testrunner/build.xml b/utilities/testrunner/build.xml index e6f5f87c9..6025d1a74 100644 --- a/utilities/testrunner/build.xml +++ b/utilities/testrunner/build.xml @@ -60,6 +60,9 @@ run - Run the tester. deprecation="true" optimize="true" source="1.6"> + + + @@ -117,9 +120,12 @@ run - Run the tester. fork="yes" dir="${acceptance.dir}" failonerror="true"> - - - + + + + + + diff --git a/utilities/testrunner/lib/commons-codec-1.3.jar b/utilities/testrunner/lib/commons-codec-1.3.jar new file mode 100644 index 000000000..957b6752a Binary files /dev/null and b/utilities/testrunner/lib/commons-codec-1.3.jar differ diff --git a/utilities/testrunner/lib/commons-httpclient-3.1.jar b/utilities/testrunner/lib/commons-httpclient-3.1.jar new file mode 100644 index 000000000..7c59774ae Binary files /dev/null and b/utilities/testrunner/lib/commons-httpclient-3.1.jar differ diff --git a/utilities/testrunner/lib/commons-logging-1.1.1.jar b/utilities/testrunner/lib/commons-logging-1.1.1.jar new file mode 100644 index 000000000..8758a96b7 Binary files /dev/null and b/utilities/testrunner/lib/commons-logging-1.1.1.jar differ diff --git a/utilities/testrunner/src/edu/cornell/mannlib/vitro/utilities/testrunner/FileHelper.java b/utilities/testrunner/src/edu/cornell/mannlib/vitro/utilities/testrunner/FileHelper.java index 38b4deba6..db11deb6b 100644 --- a/utilities/testrunner/src/edu/cornell/mannlib/vitro/utilities/testrunner/FileHelper.java +++ b/utilities/testrunner/src/edu/cornell/mannlib/vitro/utilities/testrunner/FileHelper.java @@ -2,6 +2,7 @@ package edu.cornell.mannlib.vitro.utilities.testrunner; +import java.io.BufferedReader; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; @@ -9,6 +10,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.Reader; /** * Some utility methods for dealing with files and directories. @@ -137,6 +139,26 @@ public class FileHelper { } } + /** + * Suck all the data from a {@link Reader} into a {@link String}. + */ + public static String readAll(Reader reader) throws IOException { + StringBuilder result = new StringBuilder(); + BufferedReader buffered = new BufferedReader(reader); + char[] chunk = new char[4096]; + int howMany; + + try { + while (-1 != (howMany = buffered.read(chunk))) { + result.append(chunk, 0, howMany); + } + } finally { + reader.close(); + } + + return result.toString(); + } + /** No need to instantiate this, since all methods are static. */ private FileHelper() { // Nothing to initialize. diff --git a/utilities/testrunner/src/edu/cornell/mannlib/vitro/utilities/testrunner/ModelCleaner.java b/utilities/testrunner/src/edu/cornell/mannlib/vitro/utilities/testrunner/ModelCleaner.java index cc6d4bfc9..9a698431f 100644 --- a/utilities/testrunner/src/edu/cornell/mannlib/vitro/utilities/testrunner/ModelCleaner.java +++ b/utilities/testrunner/src/edu/cornell/mannlib/vitro/utilities/testrunner/ModelCleaner.java @@ -29,13 +29,6 @@ public class ModelCleaner { this.tomcatController = tomcatController; sanityCheck(); - try { - tomcatController.stopTheWebapp(); - tomcatController.startTheWebapp(); - } catch (CommandRunnerException e) { - throw new FatalException( - "sanityCheck: Failed to stop and start Tomcat.", e); - } } private void sanityCheck() { diff --git a/utilities/testrunner/src/edu/cornell/mannlib/vitro/utilities/testrunner/ModelCleanerProperties.java b/utilities/testrunner/src/edu/cornell/mannlib/vitro/utilities/testrunner/ModelCleanerProperties.java index 285b4fd5d..c612e4fe1 100644 --- a/utilities/testrunner/src/edu/cornell/mannlib/vitro/utilities/testrunner/ModelCleanerProperties.java +++ b/utilities/testrunner/src/edu/cornell/mannlib/vitro/utilities/testrunner/ModelCleanerProperties.java @@ -10,19 +10,17 @@ import java.util.Properties; * 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_VIVO_WEBAPP_NAME = "vivo_webapp_name"; + public static final String PROP_TOMCAT_MANAGER_USERNAME = "tomcat_manager_username"; + public static final String PROP_TOMCAT_MANAGER_PASSWORD = "tomcat_manager_password"; 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_WEBAPP_DIRECTORY = "vivo_webapp_directory"; - private final String tomcatStartCommand; - private final int tomcatStartDelay; - private final String tomcatStopCommand; - private final int tomcatStopDelay; + private final String vivoWebappName; + private final String tomcatManagerUsername; + private final String tomcatManagerPassword; private final String mysqlUsername; private final String mysqlPassword; private final String mysqlDbName; @@ -33,15 +31,11 @@ public class ModelCleanerProperties { * 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.vivoWebappName = checkWebappName(props); + this.tomcatManagerUsername = getRequiredProperty(props, + PROP_TOMCAT_MANAGER_USERNAME); + this.tomcatManagerPassword = getRequiredProperty(props, + PROP_TOMCAT_MANAGER_PASSWORD); this.mysqlUsername = getRequiredProperty(props, PROP_MYSQL_USERNAME); this.mysqlPassword = getRequiredProperty(props, PROP_MYSQL_PASSWORD); @@ -50,22 +44,6 @@ public class ModelCleanerProperties { this.webappDirectory = confirmWebappDirectory(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; } @@ -82,6 +60,18 @@ public class ModelCleanerProperties { return webappDirectory; } + public String getVivoWebappName() { + return vivoWebappName; + } + + public String getTomcatManagerUsername() { + return tomcatManagerUsername; + } + + public String getTomcatManagerPassword() { + return tomcatManagerPassword; + } + /** * Get the value for this property. If there isn't one, or if it's empty, * complain. @@ -95,14 +85,19 @@ public class ModelCleanerProperties { 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 website URL must end with the webapp name. + */ + private String checkWebappName(Properties props) { + String websiteUrl = getRequiredProperty(props, + SeleniumRunnerParameters.PROP_WEBSITE_URL); + String webappName = getRequiredProperty(props, PROP_VIVO_WEBAPP_NAME); + if (!websiteUrl.endsWith(webappName)) { + throw new IllegalArgumentException("The " + PROP_VIVO_WEBAPP_NAME + + " must be the last item in the " + + SeleniumRunnerParameters.PROP_WEBSITE_URL); } + return webappName; } /** @@ -130,10 +125,9 @@ public class ModelCleanerProperties { } public String toString() { - return "\n tomcatStartCommand: " + tomcatStartCommand - + "\n tomcatStartDelay: " + tomcatStartDelay - + "\n tomcatStopCommand: " + tomcatStopCommand - + "\n tomcatStopDelay: " + tomcatStopDelay + return "\n vivoWebappName: " + vivoWebappName + + "\n tomcatManagerUsername: " + tomcatManagerUsername + + "\n tomcatManagerPassword: " + tomcatManagerPassword + "\n mysqlUsername: " + mysqlUsername + "\n mysqlPassword: " + mysqlPassword + "\n mysqlDbName: " + mysqlDbName diff --git a/utilities/testrunner/src/edu/cornell/mannlib/vitro/utilities/testrunner/TomcatController.java b/utilities/testrunner/src/edu/cornell/mannlib/vitro/utilities/testrunner/TomcatController.java index 2d89b6ea9..fbb06f9ef 100644 --- a/utilities/testrunner/src/edu/cornell/mannlib/vitro/utilities/testrunner/TomcatController.java +++ b/utilities/testrunner/src/edu/cornell/mannlib/vitro/utilities/testrunner/TomcatController.java @@ -2,140 +2,177 @@ package edu.cornell.mannlib.vitro.utilities.testrunner; -import java.util.ArrayList; -import java.util.List; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import edu.cornell.mannlib.vitro.utilities.testrunner.listener.Listener; +import edu.cornell.mannlib.vitro.utilities.testrunner.tomcat.HttpHelper; /** * Start and stop the webapp, so we can clean the database. */ public class TomcatController { + private static final Pattern PATTERN_WEBAPP_LISTING = Pattern + .compile("/(\\w+):(\\w+):"); + private final SeleniumRunnerParameters parms; private final ModelCleanerProperties properties; private final Listener listener; + private final String tomcatBaseUrl; + private final String tomcatManagerUrl; + private final String tomcatManagerUsername; + private final String tomcatManagerPassword; + private final String webappName; + public TomcatController(SeleniumRunnerParameters parms) { this.parms = parms; this.properties = parms.getModelCleanerProperties(); this.listener = parms.getListener(); + + this.webappName = properties.getVivoWebappName(); + this.tomcatBaseUrl = figureBaseUrl(); + this.tomcatManagerUrl = this.tomcatBaseUrl + "/manager"; + this.tomcatManagerUsername = properties.getTomcatManagerUsername(); + this.tomcatManagerPassword = properties.getTomcatManagerPassword(); + + checkThatTomcatIsReady(); + } + + private String figureBaseUrl() { + String url = parms.getWebsiteUrl(); + return url.substring(0, url.length() - webappName.length()); } /** - * Stop Tomcat and wait the prescribed number of seconds for it to clean up. + * Insure that Tomcat is running and has the ability to start and stop VIVO. */ - public void stopTheWebapp() throws CommandRunnerException { - String tomcatStopCommand = properties.getTomcatStopCommand(); - int tomcatStopDelay = properties.getTomcatStopDelay(); + private void checkThatTomcatIsReady() { + HttpHelper hh = new HttpHelper(); - CommandRunner runner = new CommandRunner(parms); - - 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. + // Is Tomcat responding? + if (!hh.getPage(tomcatBaseUrl)) { + throw newHttpException(hh, "Tomcat does not respond"); } - listener.webappWaitingForStop(tomcatStopDelay); - try { - Thread.sleep(tomcatStopDelay * 1000L); - } catch (InterruptedException e) { - // Just continue. + // Does the manager respond? + hh.getPage(tomcatManagerUrl + "/list"); + if (hh.getStatus() == 404) { + throw newHttpException(hh, + "Tomcat manager application does not respond. " + + "Is it installed?"); } - listener.webappStopped(); + // Do we have the correct authorization for the manager? + hh.getPage(tomcatManagerUrl + "/list", tomcatManagerUsername, + tomcatManagerPassword); + if (hh.getStatus() != 200) { + throw newHttpException(hh, "Failed to list Tomcat applications"); + } + + // Is the VIVO application running? + boolean running = isVivoRunning(hh.getResponseText()); + + if (running) { + stopTheWebapp(); + } + + // Be sure that we can start it. + startTheWebapp(); } /** - * Start Tomcat and wait for it to initialize. + * Tell Tomcat to start the webapp. Check the response. */ public void startTheWebapp() { - String tomcatStartCommand = properties.getTomcatStartCommand(); - int tomcatStartDelay = properties.getTomcatStartDelay(); + String startCommand = tomcatManagerUrl + "/start?path=/" + webappName; + listener.webappStarting(startCommand); - CommandRunner runner = new CommandRunner(parms); + HttpHelper hh = new HttpHelper(); + hh.getPage(startCommand, tomcatManagerUsername, tomcatManagerPassword); - listener.webappStarting(tomcatStartCommand); - try { - runner.runAsBackground(parseCommandLine(tomcatStartCommand)); - } catch (CommandRunnerException e) { - throw new FatalException(e); - } - - // Can't check the return code because the process shouldn't end. - - listener.webappWaitingForStart(tomcatStartDelay); - try { - Thread.sleep(tomcatStartDelay * 1000L); - } catch (InterruptedException e) { - // Just continue. + if ((hh.getStatus() != 200) || (!hh.getResponseText().startsWith("OK"))) { + listener.webappStartFailed(hh.getStatus()); + throw newHttpException(hh, "Failed to start the webapp '" + + webappName + "'"); } listener.webappStarted(); } /** - * 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. + * Tell Tomcat to stop the webapp. Check the response. */ - static List parseCommandLine(String commandLine) { - List pieces = new ArrayList(); - 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()); + public void stopTheWebapp() { + String stopCommand = tomcatManagerUrl + "/stop?path=/" + webappName; + listener.webappStopping(stopCommand); + + HttpHelper hh = new HttpHelper(); + hh.getPage(stopCommand, tomcatManagerUsername, tomcatManagerPassword); + + if ((hh.getStatus() != 200) || (!hh.getResponseText().startsWith("OK"))) { + listener.webappStopFailed(hh.getStatus()); + throw newHttpException(hh, "Failed to stop the webapp '" + + webappName + "'"); + } + + listener.webappStopped(); + } + + /** + * Is the VIVO application listed, and is it running? + */ + private boolean isVivoRunning(String responseText) { + boolean found = false; + boolean running = false; + + BufferedReader r = new BufferedReader(new StringReader(responseText)); + try { + String line; + while (null != (line = r.readLine())) { + Matcher m = PATTERN_WEBAPP_LISTING.matcher(line); + if (m.find()) { + if (this.webappName.equals(m.group(1))) { + found = true; + if ("running".equals(m.group(2))) { + running = true; + } + break; + } } - } 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); } + r.close(); + } catch (IOException e) { + // Can't happen when reading from a string. + e.printStackTrace(); } - // There is an implied delimiter at the end of the command line. - if (!inDelimiter) { - pieces.add(piece.toString()); + if (!found) { + throw new FatalException("Webapp '" + this.webappName + + "' not found in Tomcat's list of webapps: \n" + + responseText); } - // Quotes must appear in pairs - if (inQuotes) { - throw new IllegalArgumentException( - "Command line contains mismatched quotes: " + commandLine); - } + return running; + } - return pieces; + /** + * Generate a {@link FatalException} that contains a bunch of info from the + * {@link HttpHelper}. + */ + private FatalException newHttpException(HttpHelper hh, String text) { + return new FatalException(text + " status is " + hh.getStatus() + + ", response text is '" + hh.getResponseText() + "'"); } /** * The run is finished. Do we need to do anything? */ public void cleanup() { - // If we've been starting and stopping Tomcat, - // stop it one more time. - if (parms.isCleanModel()) { - try { - stopTheWebapp(); - } catch (CommandRunnerException e) { - throw new FatalException(e); - } - } - + // Leave the webapp running. } } diff --git a/utilities/testrunner/src/edu/cornell/mannlib/vitro/utilities/testrunner/tomcat/HttpHelper.java b/utilities/testrunner/src/edu/cornell/mannlib/vitro/utilities/testrunner/tomcat/HttpHelper.java new file mode 100644 index 000000000..3ecbd5613 --- /dev/null +++ b/utilities/testrunner/src/edu/cornell/mannlib/vitro/utilities/testrunner/tomcat/HttpHelper.java @@ -0,0 +1,141 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.utilities.testrunner.tomcat; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.Charset; + +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpMethod; +import org.apache.commons.httpclient.UsernamePasswordCredentials; +import org.apache.commons.httpclient.auth.AuthScope; +import org.apache.commons.httpclient.auth.BasicScheme; +import org.apache.commons.httpclient.cookie.CookiePolicy; +import org.apache.commons.httpclient.methods.GetMethod; + +import edu.cornell.mannlib.vitro.utilities.testrunner.FileHelper; + +/** + * TODO + */ +public class HttpHelper { + private String responseText; + private int status = -1; + private Throwable exception; + + /** + * Read the page at the specified URL, and set the response text and status. + */ + public boolean getPage(String url) { + this.responseText = null; + this.status = -1; + this.exception = null; + + HttpClient httpClient = new HttpClient(); + HttpMethod method = new GetMethod(url); + Reader reader = null; + try { + method.getParams().setCookiePolicy(CookiePolicy.IGNORE_COOKIES); + method.getParams().setSoTimeout(60000); + + httpClient.executeMethod(method); + + this.status = method.getStatusCode(); + + reader = new InputStreamReader(method.getResponseBodyAsStream(), + Charset.forName("UTF-8")); + this.responseText = FileHelper.readAll(reader); + + return true; + } catch (IOException e) { + this.exception = e; + return false; + } finally { + method.releaseConnection(); + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + /** + * Read the page at the specified URL, performing authentication with the + * specified username and password, and set the response text and status. + */ + public boolean getPage(String url, String username, String password) { + responseText = null; + status = -1; + this.exception = null; + + HttpClient httpClient = new HttpClient(); + httpClient.getState().setCredentials(new AuthScope(null, -1, null, "basic"), + new UsernamePasswordCredentials(username, password)); + + HttpMethod method = new GetMethod(url); + + Reader reader = null; + try { + method.getParams().setCookiePolicy(CookiePolicy.IGNORE_COOKIES); + method.getParams().setSoTimeout(60000); + + httpClient.executeMethod(method); + + this.status = method.getStatusCode(); + + reader = new InputStreamReader(method.getResponseBodyAsStream(), + Charset.forName("UTF-8")); + responseText = FileHelper.readAll(reader); + + return true; + } catch (IOException e) { + this.exception = e; + return false; + } finally { + method.releaseConnection(); + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + public int getStatus() { + return status; + } + + public String getResponseText() { + return responseText; + } + + public Throwable getException() { + return exception; + } + + public static class HttpHelperException extends Exception { + + public HttpHelperException() { + } + + public HttpHelperException(String message, Throwable cause) { + super(message, cause); + } + + public HttpHelperException(String message) { + super(message); + } + + public HttpHelperException(Throwable cause) { + super(cause); + } + + } +} diff --git a/utilities/testrunner/test/edu/cornell/mannlib/vitro/utilities/testrunner/TomcatControllerTest.java b/utilities/testrunner/test/edu/cornell/mannlib/vitro/utilities/testrunner/TomcatControllerTest.java deleted file mode 100644 index 8db9b5b9b..000000000 --- a/utilities/testrunner/test/edu/cornell/mannlib/vitro/utilities/testrunner/TomcatControllerTest.java +++ /dev/null @@ -1,51 +0,0 @@ -/* $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 TomcatControllerTest { - - // ---------------------------------------------------------------------- - // 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), - TomcatController.parseCommandLine(commandLine)); - } -}