/** * This work was created by participants in the DataONE project, and is * jointly copyrighted by participating institutions in DataONE. For * more information on DataONE, see our web site at http://dataone.org. * * Copyright ${year} * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * $Id$ */ package org.dataone.integration.webTest; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.Enumeration; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import nu.xom.Attribute; import nu.xom.Element; import nu.xom.ParsingException; import nu.xom.ValidityException; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.dataone.configuration.Settings; import org.dataone.configuration.TestSettings; import org.junit.runner.JUnitCore; /** * This class is for running the d1_integration junit integration tests through * the WebTester, and so the scope of possible tests run may be limited * programatically or by system properties. * * mnwebetester.testCase.pattern=*MNodeTier* mnwebtester.junitcore.sleep.seconds * * @author rnahf * */ public class TestRunnerHttpServlet extends HttpServlet { protected static Log log = LogFactory.getLog(TestRunnerHttpServlet.class); // private static final String TESTS_DIR = "/WEB-INF/tests"; // private static final String RESULTS_FILE_TEMPLATE = "/results.html"; private static final String RESULTS_FILE_HEAD = "/results_head.html"; // private static final String RESULTS_FILE_DIV = "/results_div.html"; private String testSelectorClassNamePattern = Settings.getConfiguration() .getString("mnwebtester.testCase.pattern", "*apiTests*"); private static final String TEST_PACKAGE_DOMAIN = "org.dataone.integration"; // private static String TEST_SELECTOR_PATTERN = "*MNodeTier*"; private boolean debug = true; private int junitSleepSeconds = 0; private static Class[] testClasses; /** * a constructor to be used for unit testings * * @param isUnitTest * @throws IOException * @throws ClassNotFoundException */ public TestRunnerHttpServlet(boolean isUnitTest) throws IOException { this(); if ( isUnitTest ) testSelectorClassNamePattern = "*MockV1ITCase"; } public TestRunnerHttpServlet() throws IOException { super(); testClasses = getClasses(TEST_PACKAGE_DOMAIN); junitSleepSeconds = Settings.getConfiguration().getInt( "mnwebtester.junitcore.sleep.seconds", junitSleepSeconds); } public void doPost(HttpServletRequest req, HttpServletResponse rsp) throws ServletException, IOException { log.debug("Entered TestRunnerHttpServlet.doPost()"); doGet(req, rsp); } /** * Handles the get call to the servlet and triggers the junit tests run */ public void doGet(HttpServletRequest req, HttpServletResponse rsp) throws ServletException, IOException { log.debug("Entered TestRunnerHttpServlet.doGet()"); log.debug("current thread: " + Thread.currentThread().getId()); if ( req.getParameter("mNodeUrl") != null ) { try { rsp.setContentType("text/html; charset=UTF-8"); executeJUnitRun(req, rsp.getOutputStream()); } catch (ClassNotFoundException e) { throw new ServletException( "Internal Configuration problem: Test classes Not Found", e); } catch (ValidityException e) { throw new ServletException( "Internal Configuration problem: HTML templates not valid", e); } catch (ParsingException e) { throw new ServletException( "Internal Configuration problem: HTML templates couldn't be parsed", e); } } else { // return error message, because we can't run rsp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Missing required parameter 'mNodeUrl'"); } } private void executeJUnitRun(HttpServletRequest req, ServletOutputStream out) throws IOException, ClassNotFoundException, ValidityException, ParsingException { String mNodeBaseUrl = req.getParameter("mNodeUrl"); String testObjectSeries = req.getParameter("testObjectSeries"); String[] selectedAPIs = req.getParameterValues("selectedAPIs"); String selectedVersion = req.getParameter("selectedVersion"); if ( log.isDebugEnabled() ) { Map params = req.getParameterMap(); log.debug(":::::::request parameters::::::"); for (Entry e : params.entrySet()) { log.debug(String.format("param: %s = %s", e.getKey(), StringUtils.join(e.getValue(), ","))); } } // start putting the response on the outputstream // spit out the header out.write("\n".getBytes("UTF-8")); out.write(IOUtils.toByteArray(this.getClass().getResourceAsStream( RESULTS_FILE_HEAD))); // spit out the visible header out.write(" \n".getBytes("UTF-8")); Element div = new Element("div"); generateURLRow(div, mNodeBaseUrl); StreamableSerializer ss = new StreamableSerializer(out); ss.writeChild(div); out.write("\n".getBytes("UTF-8")); ss.flush(); log.info("setting system property '" + TestSettings.CONTEXT_MN_URL + "' to value '" + mNodeBaseUrl + "'"); System.setProperty(TestSettings.CONTEXT_MN_URL, mNodeBaseUrl); // part of thread-aware way of passing form parameters to // ContextAwareTestCaseDataone // the other part is in ContextAwareTestCaseDataone String threadPropertyBase = "mnwebtester.thread." + Thread.currentThread().getId(); System.setProperty(threadPropertyBase + ".mn.baseurl", mNodeBaseUrl); if ( StringUtils.isNotEmpty(testObjectSeries) ) System.setProperty(threadPropertyBase + ".tierTesting.object.series", testObjectSeries); Settings.getResetConfiguration(); // to test that system properties are being received if ( debug ) System.setProperty("testSysProp", "setFromServlet"); // setup and run through the junit tests JUnitCore junit = new JUnitCore(); StreamingWebTestListener listener = new StreamingWebTestListener(out); junit.addListener(listener); for (@SuppressWarnings("rawtypes") Class testCase : getIntegrationTestClasses(testSelectorClassNamePattern)) { if ( junitSleepSeconds != 0 ) { try { log.info("sleeping between test cases for " + junitSleepSeconds + " seconds..."); Thread.sleep(junitSleepSeconds * 1000); } catch (InterruptedException e) { log.warn("sleep interrupted: " + e.getMessage()); } } if ( isASelectedAPI(selectedAPIs, selectedVersion, testCase) ) { log.info("running tests on: " + testCase.getSimpleName()); junit.run(testCase); } } listener.finishReport(); out.write(" \n".getBytes("UTF-8")); out.write("\n".getBytes("UTF-8")); out.flush(); } /** * if not working within the context of MN API tests (the selectedAPIs param * is null), this routine will return true. * Otherwise, compare the class name with the other parameters (API, version) * and return true or false. * Expecting testCase simple names of the format {API}{Version}{extra}IT */ public boolean isASelectedAPI(String[] selectedAPIs, String version, Class testCase) { /* handles the unit test case */ if ( selectedAPIs == null ) return true; String testName = testCase.getSimpleName(); log.debug("testCase name: " + testName); if ( testName.contains(version) ) { for (String selectedAPI : selectedAPIs) { if ( testName.startsWith(selectedAPI) ) { log.debug(" is Selected : " + testName); return true; } } return false; } return false; } private void generateURLRow(Element div, String url) { div.addAttribute(new Attribute("class", "greyDescr")); Element table = new Element("table"); Element tr = new Element("tr"); Element name = new Element("th"); Element rundate = new Element("td"); name.appendChild("Member Node Url: " + url); tr.appendChild(name); Date date = new Date(); rundate.appendChild(date.toString()); tr.appendChild(rundate); table.appendChild(tr); div.appendChild(table); } @SuppressWarnings("rawtypes") private Class[] getIntegrationTestClasses(String pattern) { log.debug("Java class Path: " + System.getProperty("java.class.path")); ArrayList matchingClasses = new ArrayList(); log.debug("find classes in package: " + TEST_PACKAGE_DOMAIN); log.debug("testClass.pattern = " + pattern); for (Class testClass : testClasses) { String className = testClass.getName(); log.debug("testCase: " + className); // process classes to exclude first if ( pattern == null || compareToStarPattern(pattern, className) ) { if ( !className.equals("TestRunnerHttpServlet") || className.contains("webTest") ) { log.info("Registering class: " + testClass.getName()); matchingClasses.add(testClass); } } } Collections.sort(matchingClasses, new NameComparator()); return matchingClasses.toArray(new Class[matchingClasses.size()]); } @SuppressWarnings("rawtypes") static class NameComparator implements Comparator { @Override public int compare(Class c1, Class c2) { return c1.getSimpleName().compareTo(c2.getSimpleName()); } } /** * Scans all classes accessible from the context class loader which belong * to the given package and subpackages. * * @param packageName * The base package * @return The classes * @throws ClassNotFoundException * @throws IOException */ private static Class[] getClasses(String packageName) throws IOException { ClassLoader classLoader = Thread.currentThread() .getContextClassLoader(); assert classLoader != null; String path = packageName.replace('.', '/'); Enumeration resources = classLoader.getResources(path); List dirs = new ArrayList(); while (resources.hasMoreElements()) { URL resource = resources.nextElement(); dirs.add(new File(resource.getFile())); } ArrayList> classes = new ArrayList>(); for (File directory : dirs) { classes.addAll(findClasses(directory, packageName)); } return classes.toArray(new Class[classes.size()]); } /** * Recursive method used to find all classes in a given directory and * subdirs. * * @param directory * The base directory * @param packageName * The package name for classes found inside the base directory * @return The classes * @throws ClassNotFoundException */ private static List> findClasses(File directory, String packageName) { List> classes = new ArrayList>(); if ( !directory.exists() ) { return classes; } File[] files = directory.listFiles(); log.debug("findClasses [D]: " + directory.getName()); for (File file : files) { String filename = file.getName(); if ( file.isDirectory() ) { assert !filename.contains("."); classes.addAll(findClasses(file, packageName + "." + filename)); } else if ( filename.endsWith("IT.class") || filename.endsWith("MockV1ITCase.class") ) { log.debug("findClasses : " + filename); if ( !filename.contains("$") ) { String fullFileName = packageName + '.' + filename.substring(0, filename.length() - 6); try { classes.add(Class.forName(fullFileName)); } catch (Throwable t) { log.warn("Could not add class for " + fullFileName, t); } } } } return classes; } private static boolean compareToStarPattern(String pattern, String className) { if ( pattern.startsWith("*") ) { pattern = pattern.substring(1); if ( pattern.endsWith("*") ) { pattern = pattern.substring(0, pattern.length() - 1); log.debug(" pattern: contains " + pattern); return className.contains(pattern); } else { log.debug(" pattern: endsWith " + pattern); return className.endsWith(pattern); } } else { if ( pattern.endsWith("*") ) { pattern = pattern.substring(0, pattern.length() - 1); log.debug(" pattern: startsWith " + pattern); return className.startsWith(pattern); } else { log.debug(" pattern: equals " + pattern); return className.equals(pattern); } } } }