Maven migration (first draft)
This commit is contained in:
parent
5e0329908c
commit
e1ff94ccaf
2866 changed files with 1112 additions and 616 deletions
|
@ -0,0 +1,93 @@
|
|||
/**
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class is created to work around a known bug in JarJar which did not get fixed in release 1.1.
|
||||
* See the comments in edu.cornell.mannlib.vitro.utilities.jarlist.JarLister
|
||||
*/
|
||||
|
||||
package com.tonicsystems.jarjar;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.tonicsystems.jarjar.asm.ClassReader;
|
||||
import com.tonicsystems.jarjar.ext_util.ClassHeaderReader;
|
||||
import com.tonicsystems.jarjar.ext_util.ClassPathEntry;
|
||||
import com.tonicsystems.jarjar.ext_util.ClassPathIterator;
|
||||
import com.tonicsystems.jarjar.ext_util.RuntimeIOException;
|
||||
|
||||
public class KlugedDepFind {
|
||||
private File curDir = new File(System.getProperty("user.dir"));
|
||||
|
||||
public void setCurrentDirectory(File curDir) {
|
||||
this.curDir = curDir;
|
||||
}
|
||||
|
||||
public void run(String from, String to, DepHandler handler)
|
||||
throws IOException {
|
||||
try {
|
||||
ClassHeaderReader header = new ClassHeaderReader();
|
||||
Map<String, String> classes = new HashMap<String, String>();
|
||||
ClassPathIterator cp = new ClassPathIterator(curDir, to, null);
|
||||
try {
|
||||
while (cp.hasNext()) {
|
||||
ClassPathEntry entry = cp.next();
|
||||
InputStream in = entry.openStream();
|
||||
try {
|
||||
header.read(in);
|
||||
classes.put(header.getClassName(), entry.getSource());
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error reading " + entry.getName()
|
||||
+ ": " + e.getMessage());
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
cp.close();
|
||||
}
|
||||
|
||||
handler.handleStart();
|
||||
cp = new ClassPathIterator(curDir, from, null);
|
||||
try {
|
||||
while (cp.hasNext()) {
|
||||
ClassPathEntry entry = cp.next();
|
||||
InputStream in = entry.openStream();
|
||||
try {
|
||||
new ClassReader(in).accept(new DepFindVisitor(classes,
|
||||
entry.getSource(), handler),
|
||||
ClassReader.SKIP_DEBUG
|
||||
| ClassReader.EXPAND_FRAMES);
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error reading " + entry.getName()
|
||||
+ ": " + e.getMessage());
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
cp.close();
|
||||
}
|
||||
handler.handleEnd();
|
||||
} catch (RuntimeIOException e) {
|
||||
throw (IOException) e.getCause();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,435 @@
|
|||
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||
|
||||
package edu.cornell.mannlib.vitro.utilities.containerneutral;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.ServletContextListener;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.xml.namespace.NamespaceContext;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath.XPathExpression;
|
||||
import javax.xml.xpath.XPathExpressionException;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
/**
|
||||
* Look at web.xml, and check for conditions that violate the Servlet 2.4 spec,
|
||||
* but that might not be noticed because Tomcat doesn't complain.
|
||||
*
|
||||
* ------
|
||||
*
|
||||
* Values of the <dispatcher/> tag:
|
||||
*
|
||||
* The spec permits only these values: "FORWARD", "REQUEST", "INCLUDE", "ERROR",
|
||||
* but Tomcat also allows the lower-case equivalents. GlassFish or WebLogic will
|
||||
* barf on lower-case.
|
||||
*
|
||||
* Check to see that only the upper-case values are used.
|
||||
*
|
||||
* ------
|
||||
*
|
||||
* Existence of Servlet classes:
|
||||
*
|
||||
* The spec allows the container to either load all servlets at startup, or to
|
||||
* load them when requested. Since Tomcat only loads servlet when requested, it
|
||||
* doesn't notice or complain if web.xml cites a <servlet-class/> that doesn't
|
||||
* exist, as long as it is never invoked. On the other hand, WebLogic loads all
|
||||
* serlvets at startup, and will barf if the class is not found.
|
||||
*
|
||||
* Check each <servlet-class/> to insure that the class can be loaded and
|
||||
* instantiated and assigned to HttpServlet.
|
||||
*
|
||||
* ------
|
||||
*
|
||||
* Embedded URIs in taglibs.
|
||||
*
|
||||
* I can't find this definitively in the JSP spec, but some containers complain
|
||||
* if web.xml specifies a <taglib-uri/> that conflicts with the <uri/> embedded
|
||||
* in the taglib itself. As far as I can see in the spec, the embedded <uri/>
|
||||
* tag is not required or referenced unless we are using
|
||||
* "Implicit Map Entries From TLDs", which in turn is only relevant for TLDs
|
||||
* packaged in JAR files. So, I can't find support for this complaint, but it
|
||||
* seems a reasonable one.
|
||||
*
|
||||
* Check each <taglib/> specified in web.xml. If the taglib has an embedded
|
||||
* <uri/> tag, it should match the <taglib-uri/> from web.xml.
|
||||
*
|
||||
* ------
|
||||
*
|
||||
* Existence of Listener and Filter classes.
|
||||
*
|
||||
* As far as I can tell, there is no ambiguity here, and every container will
|
||||
* complain if any of the <listener-class/> or <filter-class/> entries are
|
||||
* unsuitable. I check them anyway, since the mechanism was already assembled
|
||||
* for checking <servlet-class/> entries.
|
||||
*
|
||||
* Check each <listener-class/> to insure that the class can be loaded and
|
||||
* instantiated and assigned to ServletContextListener.
|
||||
*
|
||||
* Check each <filter-class/> to insure that the class can be loaded and
|
||||
* instantiated and assigned to Filter.
|
||||
*
|
||||
* ------
|
||||
*
|
||||
* A <servlet/> tag for every <servlet-mapping/> tag
|
||||
*
|
||||
* I can't find a mention of this in the spec, but Tomcat complains and refuses
|
||||
* to load the app if there is a <servlet-mapping/> tag whose <servlet-name/> is
|
||||
* not matched by a <servlet-name/> in a <servlet/> tag.
|
||||
*
|
||||
* Get sets of all <servlet-name/> tags that are specified in <servlet/> and
|
||||
* <servlet-mapping/> tags. There should not be any names in the
|
||||
* servlet-mappings that are not in the servlets.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* Although this class is executed as a JUnit test, it doesn't have the usual
|
||||
* structure for a unit test.
|
||||
*
|
||||
* In order to produce the most diagnostic information, the test does not abort
|
||||
* on the first failure. Rather, failure messages are accumulated until all
|
||||
* checks have been performed, and the test list all such messages on failure.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
*
|
||||
* Since this is not executed as part of the standard Vitro unit tests, it also
|
||||
* cannot use the standard logging mechanism. Log4J has not been initialized.
|
||||
*
|
||||
*/
|
||||
public class CheckContainerNeutrality {
|
||||
private static final String PROPERTY_WEBAPP_DIR = "CheckContainerNeutrality.webapp.dir";
|
||||
|
||||
private static DocumentBuilder docBuilder;
|
||||
private static XPath xpath;
|
||||
|
||||
@BeforeClass
|
||||
public static void createDocBuilder() {
|
||||
try {
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory
|
||||
.newInstance();
|
||||
factory.setNamespaceAware(true); // never forget this!
|
||||
docBuilder = factory.newDocumentBuilder();
|
||||
} catch (ParserConfigurationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void createXPath() {
|
||||
xpath = XPathFactory.newInstance().newXPath();
|
||||
xpath.setNamespaceContext(new StupidNamespaceContext());
|
||||
}
|
||||
|
||||
private File webappDir;
|
||||
private File webXmlFile;
|
||||
private Document webXmlDoc;
|
||||
private List<String> messages;
|
||||
|
||||
@Before
|
||||
public void setup() throws SAXException, IOException {
|
||||
String webappDirPath = System.getProperty(PROPERTY_WEBAPP_DIR);
|
||||
if (webappDirPath == null) {
|
||||
fail("System property '" + PROPERTY_WEBAPP_DIR
|
||||
+ "' was not provided.");
|
||||
}
|
||||
webappDir = new File(webappDirPath);
|
||||
if (!webappDir.isDirectory()) {
|
||||
fail("'" + webappDirPath + "' is not a directory");
|
||||
}
|
||||
webXmlFile = new File(webappDir, "WEB-INF/web.xml");
|
||||
if (!webXmlFile.isFile()) {
|
||||
fail("Can't find '" + webXmlFile.getAbsolutePath() + "'");
|
||||
}
|
||||
|
||||
webXmlDoc = docBuilder.parse(webXmlFile);
|
||||
|
||||
messages = new ArrayList<String>();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Tests
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
public void checkAll() throws IOException {
|
||||
checkDispatcherValues();
|
||||
checkServletClasses();
|
||||
checkListenerClasses();
|
||||
checkFilterClasses();
|
||||
checkTaglibLocations();
|
||||
checkServletNames();
|
||||
|
||||
if (!messages.isEmpty()) {
|
||||
fail("Found these problems with '" + webXmlFile.getCanonicalPath()
|
||||
+ "'\n " + StringUtils.join(messages, "\n "));
|
||||
}
|
||||
}
|
||||
|
||||
private void checkDispatcherValues() {
|
||||
List<String> okValues = Arrays.asList(new String[] { "FORWARD",
|
||||
"REQUEST", "INCLUDE", "ERROR" });
|
||||
for (Node n : findNodes("//j2ee:dispatcher")) {
|
||||
String text = n.getTextContent();
|
||||
if (!okValues.contains(text)) {
|
||||
messages.add("<dispatcher>" + text
|
||||
+ "</dispatcher> is not valid. Acceptable values are "
|
||||
+ okValues);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkServletClasses() {
|
||||
for (Node n : findNodes("//j2ee:servlet-class")) {
|
||||
String text = n.getTextContent();
|
||||
String problem = confirmClassNameIsValid(text, HttpServlet.class);
|
||||
if (problem != null) {
|
||||
messages.add("<servlet-class>" + text
|
||||
+ "</servlet-class> is not valid: " + problem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkListenerClasses() {
|
||||
for (Node n : findNodes("//j2ee:listener-class")) {
|
||||
String text = n.getTextContent();
|
||||
String problem = confirmClassNameIsValid(text,
|
||||
ServletContextListener.class);
|
||||
if (problem != null) {
|
||||
messages.add("<listener-class>" + text
|
||||
+ "</listener-class> is not valid: " + problem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkFilterClasses() {
|
||||
for (Node n : findNodes("//j2ee:filter-class")) {
|
||||
String text = n.getTextContent();
|
||||
String problem = confirmClassNameIsValid(text, Filter.class);
|
||||
if (problem != null) {
|
||||
messages.add("<filter-class>" + text
|
||||
+ "</filter-class> is not valid: " + problem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkTaglibLocations() {
|
||||
for (Node n : findNodes("//j2ee:jsp-config/j2ee:taglib")) {
|
||||
String taglibUri = findNode("j2ee:taglib-uri", n).getTextContent();
|
||||
String taglibLocation = findNode("j2ee:taglib-location", n)
|
||||
.getTextContent();
|
||||
// System.out.println("taglibUri='" + taglibUri
|
||||
// + "', taglibLocation='" + taglibLocation + "'");
|
||||
String message = checkTaglibUri(taglibUri, taglibLocation);
|
||||
if (message != null) {
|
||||
messages.add(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkServletNames() {
|
||||
Set<String> servletNames = new HashSet<String>();
|
||||
for (Node n : findNodes("//j2ee:servlet/j2ee:servlet-name")) {
|
||||
servletNames.add(n.getTextContent());
|
||||
}
|
||||
|
||||
Set<String> servletMappingNames = new HashSet<String>();
|
||||
for (Node n : findNodes("//j2ee:servlet-mapping/j2ee:servlet-name")) {
|
||||
servletMappingNames.add(n.getTextContent());
|
||||
}
|
||||
|
||||
servletMappingNames.removeAll(servletNames);
|
||||
for (String name : servletMappingNames) {
|
||||
messages.add("There is a <servlet-mapping> tag for <servlet-name>"
|
||||
+ name + "</servlet-name>, but there is "
|
||||
+ "no matching <servlet> tag.");
|
||||
}
|
||||
}
|
||||
|
||||
private String checkTaglibUri(String taglibUri, String taglibLocation) {
|
||||
File taglibFile = new File(webappDir, taglibLocation);
|
||||
if (!taglibFile.isFile()) {
|
||||
return "File '" + taglibLocation + "' can't be found ('"
|
||||
+ taglibFile.getAbsolutePath() + "')";
|
||||
}
|
||||
|
||||
Document taglibDoc;
|
||||
try {
|
||||
taglibDoc = docBuilder.parse(taglibFile);
|
||||
} catch (SAXException e) {
|
||||
return "Failed to parse the taglib file '" + taglibFile + "': " + e;
|
||||
} catch (IOException e) {
|
||||
return "Failed to parse the taglib file '" + taglibFile + "': " + e;
|
||||
}
|
||||
|
||||
List<Node> uriNodes = findNodes("/j2ee:taglib/j2ee:uri",
|
||||
taglibDoc.getDocumentElement());
|
||||
// System.out.println("uriNodes: " + uriNodes);
|
||||
if (uriNodes.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (uriNodes.size() > 1) {
|
||||
return "taglib '" + taglibLocation + "' contains "
|
||||
+ uriNodes.size()
|
||||
+ " <uri> nodes. Expecting no more than 1";
|
||||
}
|
||||
|
||||
String embeddedUri = uriNodes.get(0).getTextContent();
|
||||
if (taglibUri.equals(embeddedUri)) {
|
||||
return null;
|
||||
} else {
|
||||
return "URI in taglib doesn't match the one in web.xml: taglib='"
|
||||
+ taglibLocation + "', internal URI='"
|
||||
+ uriNodes.get(0).getTextContent()
|
||||
+ "', URI from web.xml='" + taglibUri + "'";
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Helper methods
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Search for an Xpath in web.xml, returning a handy list.
|
||||
*/
|
||||
private List<Node> findNodes(String pattern) {
|
||||
return findNodes(pattern, webXmlDoc.getDocumentElement());
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for an Xpath within a node of web.xml, returning a handy list.
|
||||
*/
|
||||
private List<Node> findNodes(String pattern, Node context) {
|
||||
try {
|
||||
XPathExpression xpe = xpath.compile(pattern);
|
||||
NodeList nodes = (NodeList) xpe.evaluate(context,
|
||||
XPathConstants.NODESET);
|
||||
List<Node> list = new ArrayList<Node>();
|
||||
for (int i = 0; i < nodes.getLength(); i++) {
|
||||
list.add(nodes.item(i));
|
||||
}
|
||||
return list;
|
||||
} catch (XPathExpressionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for an Xpath within a node of web.xml, returning a single node or
|
||||
* throwing an exception.
|
||||
*/
|
||||
private Node findNode(String pattern, Node context) {
|
||||
List<Node> list = findNodes(pattern, context);
|
||||
if (list.size() != 1) {
|
||||
throw new RuntimeException("Expecting 1 node, but found "
|
||||
+ list.size() + " nodes using '" + pattern + "'");
|
||||
} else {
|
||||
return list.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the supplied className can be instantiated with a
|
||||
* zero-argument constructor, and assigned to a variable of the target
|
||||
* class.
|
||||
*/
|
||||
private String confirmClassNameIsValid(String className,
|
||||
Class<?> targetClass) {
|
||||
try {
|
||||
Class<?> specifiedClass = Class.forName(className);
|
||||
Object o = specifiedClass.newInstance();
|
||||
if (!targetClass.isInstance(o)) {
|
||||
return specifiedClass.getSimpleName()
|
||||
+ " is not a subclass of "
|
||||
+ targetClass.getSimpleName() + ".";
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
return "The class does not exist.";
|
||||
} catch (InstantiationException e) {
|
||||
return "The class does not have a public constructor "
|
||||
+ "that takes zero arguments.";
|
||||
} catch (IllegalAccessException e) {
|
||||
return "The class does not have a public constructor "
|
||||
+ "that takes zero arguments.";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump the first 20 nodes of an XML context, excluding comments and blank
|
||||
* text nodes.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private int dumpXml(Node xmlNode, int... parms) {
|
||||
int remaining = (parms.length == 0) ? 20 : parms[0];
|
||||
int level = (parms.length < 2) ? 1 : parms[1];
|
||||
|
||||
Node n = xmlNode;
|
||||
|
||||
if (Node.COMMENT_NODE == n.getNodeType()) {
|
||||
return 0;
|
||||
}
|
||||
if (Node.TEXT_NODE == n.getNodeType()) {
|
||||
if (StringUtils.isBlank(n.getTextContent())) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int used = 1;
|
||||
|
||||
System.out.println(StringUtils.repeat("-->", level) + n);
|
||||
NodeList nl = n.getChildNodes();
|
||||
for (int i = 0; (i < nl.getLength() && remaining > used); i++) {
|
||||
used += dumpXml(nl.item(i), remaining - used, level + 1);
|
||||
}
|
||||
return used;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Helper classes
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
private static class StupidNamespaceContext implements NamespaceContext {
|
||||
@Override
|
||||
public String getNamespaceURI(String prefix) {
|
||||
if ("j2ee".equals(prefix)) {
|
||||
return "http://java.sun.com/xml/ns/j2ee";
|
||||
} else {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPrefix(String namespaceURI) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<?> getPrefixes(String namespaceURI) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,198 @@
|
|||
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||
|
||||
package edu.cornell.mannlib.vitro.utilities.jarlist;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import com.tonicsystems.jarjar.AbstractDepHandler;
|
||||
import com.tonicsystems.jarjar.DepHandler;
|
||||
import com.tonicsystems.jarjar.KlugedDepFind;
|
||||
|
||||
/**
|
||||
* This takes the place of the JarJar main routine, in doing a Find operation.
|
||||
*
|
||||
* One thing this lets us do is to call KlugedDepFind instead of DepFind.
|
||||
* KlugedDepFind was created because JarJar had a known bug that wasn't fixed in
|
||||
* the latest release. (see http://code.google.com/p/jarjar/issues/detail?id=6).
|
||||
* I had to put KlugedDepFind into the com.tonicsystems.jarjar package so it
|
||||
* wauld have access to DepFindVisitor, which is package-private.
|
||||
*
|
||||
* The other thing we can do is to provide a custom DepHandler which records the
|
||||
* dependencies directly instead of writing them to a file which we would need
|
||||
* to parse. Since we have the dependencies in a data structure, it's easy to
|
||||
* walk the tree and find out what JARs are required, even through several
|
||||
* layers of dependency.
|
||||
*
|
||||
* When calling this, pass 2 arguments. The first is the path to the JAR which
|
||||
* contains the Vitro (or VIVO) classes. The second is the path to the directory
|
||||
* that contains the JARs. (shouldn't end with a slash)
|
||||
*
|
||||
* There is a list of JARs which we know we need but which aren't shown by the
|
||||
* analysis. For example, the MySQL driver is loaded dynamically by name, so an
|
||||
* analysis of the class files in the JARs won't show that it is used. For now,
|
||||
* these known dependencies are hard-coded, but it would be nice to read them
|
||||
* from a data file instead.
|
||||
*/
|
||||
public class JarLister {
|
||||
/**
|
||||
* <pre>
|
||||
*
|
||||
* What I originally wanted to do was this:
|
||||
*
|
||||
* <target name="jarlist" depends="jar" description="Figure out what JARs are needed">
|
||||
* <java classname="com.tonicsystems.jarjar.Main" fork="no" failonerror="true">
|
||||
* <classpath refid="utility.run.classpath" />
|
||||
* <arg value="find" />
|
||||
* <arg value="jar" />
|
||||
* <arg value="${build.dir}/${ant.project.name}.jar" />
|
||||
* <arg value="${appbase.dir}/lib/*" />
|
||||
* </java>
|
||||
* </target>
|
||||
*
|
||||
* I ended up with this instead:
|
||||
*
|
||||
* <target name="jarlist" depends="jar" description="Figure out what JARs are needed">
|
||||
* <java classname="edu.cornell.mannlib.vitro.utilities.jarlist.JarLister" fork="no" failonerror="true">
|
||||
* <classpath refid="utility.run.classpath" />
|
||||
* <arg value="${build.dir}/${ant.project.name}.jar" />
|
||||
* <arg value="${appbase.dir}/lib" />
|
||||
* <arg value="${appbase.dir}/config/jarlist/known_dependencies.txt" />
|
||||
* </java>
|
||||
* </target>
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
|
||||
private final String topJar;
|
||||
private final String libDirectory;
|
||||
private final List<String> knownDependencies;
|
||||
|
||||
private final Map<String, Set<String>> dependencyMap = new HashMap<String, Set<String>>();
|
||||
private final Set<String> dependencySet = new TreeSet<String>();
|
||||
|
||||
public JarLister(String[] args) throws IOException {
|
||||
topJar = args[0];
|
||||
libDirectory = args[1];
|
||||
knownDependencies = Collections
|
||||
.unmodifiableList(readKnownDependencies(args[2]));
|
||||
}
|
||||
|
||||
private List<String> readKnownDependencies(String knownDependenciesFilename)
|
||||
throws IOException {
|
||||
List<String> list = new ArrayList<String>();
|
||||
|
||||
BufferedReader r = new BufferedReader(new FileReader(
|
||||
knownDependenciesFilename));
|
||||
|
||||
String line;
|
||||
while (null != (line = r.readLine())) {
|
||||
line = line.trim();
|
||||
if (!(line.startsWith("#") || line.isEmpty())) {
|
||||
list.add(line);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public void runDepFind() throws IOException {
|
||||
DepHandler handler = new AbstractDepHandler(DepHandler.LEVEL_JAR) {
|
||||
@Override
|
||||
public void handle(String from, String to) {
|
||||
addToMap(from, to);
|
||||
}
|
||||
};
|
||||
|
||||
String fullPath = topJar + ":" + libDirectory + "/*";
|
||||
|
||||
new KlugedDepFind().run(fullPath, fullPath, handler);
|
||||
}
|
||||
|
||||
private void addToMap(String fromPath, String toPath) {
|
||||
String fromName = new File(fromPath).getName();
|
||||
String toName = new File(toPath).getName();
|
||||
|
||||
if (!dependencyMap.containsKey(fromName)) {
|
||||
dependencyMap.put(fromName, new HashSet<String>());
|
||||
}
|
||||
dependencyMap.get(fromName).add(toName);
|
||||
// System.out.println("Adding " + fromName + " ==> " + toName);
|
||||
}
|
||||
|
||||
public void populateDependencySet() {
|
||||
String topJarName = new File(topJar).getName();
|
||||
addDependenciesFor(topJarName);
|
||||
|
||||
for (String known : knownDependencies) {
|
||||
dependencySet.add(known);
|
||||
addDependenciesFor(known);
|
||||
}
|
||||
}
|
||||
|
||||
private void addDependenciesFor(String name) {
|
||||
if (!dependencyMap.containsKey(name)) {
|
||||
return;
|
||||
}
|
||||
for (String depend : dependencyMap.get(name)) {
|
||||
if (!dependencySet.contains(depend)) {
|
||||
dependencySet.add(depend);
|
||||
// System.out.println("Depend: " + depend);
|
||||
addDependenciesFor(depend);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void dumpDependencySet(PrintWriter w) {
|
||||
w.println("--------------------");
|
||||
w.println("Known required JARs");
|
||||
w.println("--------------------");
|
||||
for (String d : knownDependencies) {
|
||||
w.println(" " + d);
|
||||
}
|
||||
w.println();
|
||||
|
||||
w.println("--------------------");
|
||||
w.println("Dependent JARs");
|
||||
w.println("--------------------");
|
||||
for (String d : dependencySet) {
|
||||
w.println(" " + d);
|
||||
}
|
||||
w.println();
|
||||
|
||||
File libDir = new File(libDirectory);
|
||||
SortedSet<String> unused = new TreeSet<String>(Arrays.asList(libDir
|
||||
.list()));
|
||||
unused.removeAll(dependencySet);
|
||||
|
||||
w.println("--------------------");
|
||||
w.println("Unused JARs");
|
||||
w.println("--------------------");
|
||||
for (String d : unused) {
|
||||
w.println(" " + d);
|
||||
}
|
||||
w.println();
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
JarLister jl = new JarLister(args);
|
||||
jl.runDepFind();
|
||||
jl.populateDependencySet();
|
||||
|
||||
PrintWriter output = new PrintWriter(System.out, true);
|
||||
jl.dumpDependencySet(output);
|
||||
output.close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||
|
||||
package edu.cornell.mannlib.vitro.utilities.revisioninfo;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.StringWriter;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A harness that runs a system-level command.
|
||||
*
|
||||
* No provision is made for standard input.
|
||||
*
|
||||
* The standard output and standard error streams are asynchronously read, so
|
||||
* the sub-process will not block on full buffers. Warning: if either of these
|
||||
* streams contain more data than can fit into a String, then we will have a
|
||||
* problem.
|
||||
*/
|
||||
public class ProcessRunner {
|
||||
private int returnCode;
|
||||
private String stdOut = "";
|
||||
private String stdErr = "";
|
||||
private File workingDirectory;
|
||||
|
||||
private final Map<String, String> environmentAdditions = new HashMap<String, String>();
|
||||
|
||||
/** Set the directory that the command will run in. */
|
||||
public void setWorkingDirectory(File workingDirectory) {
|
||||
this.workingDirectory = workingDirectory;
|
||||
}
|
||||
|
||||
/** Add (or replace) any environment variable. */
|
||||
public void setEnvironmentVariable(String key, String value) {
|
||||
this.environmentAdditions.put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the command.
|
||||
*
|
||||
* @param command
|
||||
* a list containing the operating system program and its
|
||||
* arguments. See
|
||||
* {@link java.lang.ProcessBuilder#ProcessBuilder(List)}.
|
||||
*/
|
||||
public void run(List<String> command) throws ProcessException {
|
||||
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());
|
||||
StreamEater errorEater = new StreamEater(process.getErrorStream());
|
||||
|
||||
this.returnCode = process.waitFor();
|
||||
|
||||
outputEater.join();
|
||||
this.stdOut = outputEater.getContents();
|
||||
|
||||
errorEater.join();
|
||||
this.stdErr = errorEater.getContents();
|
||||
} catch (IOException e) {
|
||||
throw new ProcessException("Exception when handling sub-process:",
|
||||
e);
|
||||
} catch (InterruptedException e) {
|
||||
throw new ProcessException("Exception when handling sub-process:",
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
public int getReturnCode() {
|
||||
return returnCode;
|
||||
}
|
||||
|
||||
public String getStdErr() {
|
||||
return stdErr;
|
||||
}
|
||||
|
||||
public String getStdOut() {
|
||||
return stdOut;
|
||||
}
|
||||
|
||||
/**
|
||||
* A thread that reads an InputStream until it reaches end of file, then
|
||||
* closes the stream.
|
||||
*/
|
||||
private static class StreamEater extends Thread {
|
||||
private final InputStream stream;
|
||||
|
||||
private final StringWriter contents = new StringWriter();
|
||||
|
||||
private final byte[] buffer = new byte[4096];
|
||||
|
||||
public StreamEater(InputStream stream) {
|
||||
this.stream = stream;
|
||||
this.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
int howMany = 0;
|
||||
while (true) {
|
||||
howMany = stream.read(buffer);
|
||||
if (howMany > 0) {
|
||||
contents.write(new String(buffer, 0, howMany));
|
||||
} else if (howMany == 0) {
|
||||
Thread.yield();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
stream.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getContents() {
|
||||
return contents.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates a problem when dealing with a spawned sub-process.
|
||||
*/
|
||||
public static class ProcessException extends Exception {
|
||||
|
||||
public ProcessException() {
|
||||
}
|
||||
|
||||
public ProcessException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ProcessException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public ProcessException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,253 @@
|
|||
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||
|
||||
package edu.cornell.mannlib.vitro.utilities.revisioninfo;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.EOFException;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import edu.cornell.mannlib.vitro.utilities.revisioninfo.ProcessRunner.ProcessException;
|
||||
|
||||
/**
|
||||
* Get release and revision information to display on screen. Put this
|
||||
* information into a single line and append it to the specified file.
|
||||
*
|
||||
* Ask Git for the information. If Git is available, and if this is a working
|
||||
* directory, then we can build the info from the responses we get from
|
||||
* "git describe", "git symbolic-ref" and "git log".
|
||||
*
|
||||
* If that doesn't work, read the information from the "revisionInfo" file in
|
||||
* the product directory. Presumably, that file was created when the source was
|
||||
* exported from Git.
|
||||
*
|
||||
* If that doesn't work either, return something like this:
|
||||
* "productName ~ unknown ~ unknown"
|
||||
*/
|
||||
public class RevisionInfoBuilder {
|
||||
|
||||
/**
|
||||
* Indicates a problem with the command-line arguments.
|
||||
*/
|
||||
private static class UsageException extends Exception {
|
||||
UsageException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An object that holds the revision information and a message about how we
|
||||
* obtained it.
|
||||
*/
|
||||
private static class Results {
|
||||
final String message;
|
||||
final String infoLine;
|
||||
|
||||
Results(String message, String infoLine) {
|
||||
this.message = message;
|
||||
this.infoLine = infoLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return message + ": " + infoLine;
|
||||
}
|
||||
}
|
||||
|
||||
private static final String GIT_DIRECTORY_NAME = ".git";
|
||||
private static final String[] GIT_DESCRIBE_COMMAND = { "git", "describe" };
|
||||
private static final String[] GIT_SREF_COMMAND = { "git", "symbolic-ref",
|
||||
"HEAD" };
|
||||
private static final String[] GIT_LOG_COMMAND = { "git", "log",
|
||||
"--pretty=format:%h", "-1" };
|
||||
private static final String INFO_LINE_DELIMITER = " ~ ";
|
||||
private static final String REVISION_INFO_FILENAME = "revisionInfo";
|
||||
|
||||
private final String productName;
|
||||
private final File productDirectory;
|
||||
private final File resultFile;
|
||||
|
||||
private Results results;
|
||||
|
||||
public RevisionInfoBuilder(String[] args) throws UsageException {
|
||||
if (args.length != 3) {
|
||||
throw new UsageException(
|
||||
"RevisionInfoBuilder requires 3 arguments, not "
|
||||
+ args.length);
|
||||
}
|
||||
|
||||
productName = args[0];
|
||||
productDirectory = new File(args[1]);
|
||||
resultFile = new File(args[2]);
|
||||
|
||||
if (!productDirectory.isDirectory()) {
|
||||
throw new UsageException("Directory '"
|
||||
+ productDirectory.getAbsolutePath() + "' does not exist.");
|
||||
}
|
||||
|
||||
if (!resultFile.getParentFile().exists()) {
|
||||
throw new UsageException("Result file '"
|
||||
+ resultFile.getAbsolutePath()
|
||||
+ "' does not exist, and we can't create it "
|
||||
+ "because it's parent directory doesn't exist either.");
|
||||
}
|
||||
}
|
||||
|
||||
private void buildInfo() {
|
||||
results = buildInfoFromGit();
|
||||
if (results == null) {
|
||||
results = buildInfoFromFile();
|
||||
}
|
||||
if (results == null) {
|
||||
results = buildDummyInfo();
|
||||
}
|
||||
}
|
||||
|
||||
private Results buildInfoFromGit() {
|
||||
if (!isThisAGitWorkspace()) {
|
||||
System.out.println("Not a git workspace");
|
||||
return null;
|
||||
}
|
||||
|
||||
String release = assembleReleaseNameFromGit();
|
||||
if (release == null) {
|
||||
System.out.println("Couldn't get release name from Git");
|
||||
}
|
||||
|
||||
String revision = obtainCommitIdFromGit();
|
||||
if (revision == null) {
|
||||
System.out.println("Couldn't get commit ID from Git");
|
||||
}
|
||||
|
||||
if ((revision == null) && (release == null)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Results("Info from Git", buildLine(release, revision));
|
||||
}
|
||||
|
||||
private boolean isThisAGitWorkspace() {
|
||||
File gitDirectory = new File(productDirectory, GIT_DIRECTORY_NAME);
|
||||
return gitDirectory.isDirectory();
|
||||
}
|
||||
|
||||
private String assembleReleaseNameFromGit() {
|
||||
String describeResponse = runSubProcess(GIT_DESCRIBE_COMMAND);
|
||||
String srefResponse = runSubProcess(GIT_SREF_COMMAND);
|
||||
return parseReleaseName(describeResponse, srefResponse);
|
||||
}
|
||||
|
||||
private String obtainCommitIdFromGit() {
|
||||
String logResponse = runSubProcess(GIT_LOG_COMMAND);
|
||||
return parseLogResponse(logResponse);
|
||||
}
|
||||
|
||||
private String parseReleaseName(String describeResponse, String srefResponse) {
|
||||
if (describeResponse != null) {
|
||||
return describeResponse.trim() + " tag";
|
||||
} else if (srefResponse != null) {
|
||||
return srefResponse.substring(srefResponse.lastIndexOf('/') + 1)
|
||||
.trim() + " branch";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String parseLogResponse(String logResponse) {
|
||||
return logResponse;
|
||||
}
|
||||
|
||||
private String runSubProcess(String[] cmdArray) {
|
||||
List<String> command = Arrays.asList(cmdArray);
|
||||
try {
|
||||
ProcessRunner runner = new ProcessRunner();
|
||||
runner.setWorkingDirectory(productDirectory);
|
||||
|
||||
runner.run(command);
|
||||
|
||||
int rc = runner.getReturnCode();
|
||||
if (rc != 0) {
|
||||
throw new ProcessRunner.ProcessException("Return code from "
|
||||
+ command + " was " + rc);
|
||||
}
|
||||
|
||||
String output = runner.getStdOut();
|
||||
// System.err.println(command + " response was '" + output + "'");
|
||||
return output;
|
||||
} catch (ProcessException e) {
|
||||
// System.out.println(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Results buildInfoFromFile() {
|
||||
try {
|
||||
File revisionInfoFile = new File(productDirectory,
|
||||
REVISION_INFO_FILENAME);
|
||||
BufferedReader reader = new BufferedReader(new FileReader(
|
||||
revisionInfoFile));
|
||||
|
||||
String release = reader.readLine();
|
||||
if (release == null) {
|
||||
throw new EOFException("No release line in file.");
|
||||
}
|
||||
|
||||
String revision = reader.readLine();
|
||||
if (revision == null) {
|
||||
throw new EOFException("No revision line in file.");
|
||||
}
|
||||
|
||||
return new Results("Info from file", buildLine(release, revision));
|
||||
} catch (IOException e) {
|
||||
System.out.println("No information from file: " + e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Results buildDummyInfo() {
|
||||
String line = buildLine(null, null);
|
||||
return new Results("Using dummy info", line);
|
||||
}
|
||||
|
||||
private String buildLine(String release, String revision) {
|
||||
if (release == null) {
|
||||
release = "unknown";
|
||||
}
|
||||
if (revision == null) {
|
||||
revision = "unknown";
|
||||
}
|
||||
return productName + INFO_LINE_DELIMITER + release.trim()
|
||||
+ INFO_LINE_DELIMITER + revision.trim();
|
||||
}
|
||||
|
||||
private void writeLine() throws IOException {
|
||||
Writer writer = null;
|
||||
writer = new FileWriter(resultFile, true);
|
||||
writer.write(results.infoLine + "\n");
|
||||
writer.close();
|
||||
|
||||
System.out.println(results);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
RevisionInfoBuilder builder = new RevisionInfoBuilder(args);
|
||||
builder.buildInfo();
|
||||
builder.writeLine();
|
||||
} catch (UsageException e) {
|
||||
System.err.println(e);
|
||||
System.err.println("usage: RevisionInfoBuilder [product_name] "
|
||||
+ "[product_directory] [output_file]");
|
||||
System.exit(1);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,346 @@
|
|||
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||
|
||||
package edu.cornell.mannlib.vitro.utilities.testing;
|
||||
|
||||
import static edu.cornell.mannlib.vitro.utilities.testing.VitroTestRunner.ReportLevel.BRIEF;
|
||||
import static edu.cornell.mannlib.vitro.utilities.testing.VitroTestRunner.ReportLevel.FULL;
|
||||
import static edu.cornell.mannlib.vitro.utilities.testing.VitroTestRunner.ReportLevel.MORE;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.runner.Description;
|
||||
import org.junit.runner.Result;
|
||||
import org.junit.runner.notification.Failure;
|
||||
import org.junit.runner.notification.RunListener;
|
||||
|
||||
import edu.cornell.mannlib.vitro.utilities.testing.VitroTestRunner.ReportLevel;
|
||||
|
||||
/**
|
||||
* Listen to events as they come from the JUnit test runner. The events from the
|
||||
* lifecycle methods are broken down into semantic chunks and executed. Three
|
||||
* levels of output are available.
|
||||
*
|
||||
* On the surface, JUnit treats "failures" (failed assertions) the same as
|
||||
* "errors" (unexpected exceptions). We're going to distinguish between them.
|
||||
*
|
||||
* @author jeb228
|
||||
*/
|
||||
public class VitroTestRunListener extends RunListener {
|
||||
private final ReportLevel reportLevel;
|
||||
|
||||
private int classCount;
|
||||
private int testsTotal;
|
||||
private int errorsTotal;
|
||||
private int failuresTotal;
|
||||
private int ignoresTotal;
|
||||
private long overallStartTime;
|
||||
|
||||
private Class<?> currentClass;
|
||||
private int testsCurrentClass;
|
||||
private int errorsCurrentClass;
|
||||
private int failuresCurrentClass;
|
||||
private int ignoresCurrentClass;
|
||||
private long classStartTime;
|
||||
|
||||
private String currentTest;
|
||||
private boolean testHadError;
|
||||
private boolean testFailed;
|
||||
private boolean testIgnored;
|
||||
private long testStartTime;
|
||||
|
||||
public VitroTestRunListener(ReportLevel reportLevel) {
|
||||
this.reportLevel = reportLevel;
|
||||
}
|
||||
|
||||
/** Did any of the tests fail or have errors? */
|
||||
public boolean didEverythingPass() {
|
||||
return (failuresTotal == 0) && (errorsTotal == 0);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Life-cycle methods that will be called by the test runner.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void testRunStarted(Description description) throws Exception {
|
||||
openTestRun();
|
||||
reportTestRunStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testStarted(Description description) throws Exception {
|
||||
if (currentClass != description.getTestClass()) {
|
||||
if (currentClass != null) {
|
||||
closeCurrentClass();
|
||||
reportCurrentClass();
|
||||
}
|
||||
|
||||
openCurrentClass(description);
|
||||
reportCurrentClassStart();
|
||||
}
|
||||
|
||||
openCurrentTest(description);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testAssumptionFailure(Failure failure) {
|
||||
if (isError(failure)) {
|
||||
testHadError = true;
|
||||
reportError(failure);
|
||||
} else {
|
||||
testFailed = true;
|
||||
reportFailure(failure);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testFailure(Failure failure) throws Exception {
|
||||
if (isError(failure)) {
|
||||
testHadError = true;
|
||||
reportError(failure);
|
||||
} else {
|
||||
testFailed = true;
|
||||
reportFailure(failure);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testFinished(Description description) throws Exception {
|
||||
closeCurrentTest();
|
||||
reportCurrentTest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testIgnored(Description description) throws Exception {
|
||||
testStarted(description);
|
||||
testIgnored = true;
|
||||
testFinished(description);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testRunFinished(Result result) throws Exception {
|
||||
if (currentClass != null) {
|
||||
closeCurrentClass();
|
||||
reportCurrentClass();
|
||||
}
|
||||
closeTestRun();
|
||||
reportTestRun();
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Handling the logical events.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private void openTestRun() {
|
||||
overallStartTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
private void closeTestRun() {
|
||||
// Nothing to close.
|
||||
}
|
||||
|
||||
private void reportTestRunStart() {
|
||||
if (reportLevel == FULL) {
|
||||
System.out
|
||||
.println("Starting test run at " + time(overallStartTime));
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
if (reportLevel == MORE) {
|
||||
System.out
|
||||
.println("Starting test run at " + time(overallStartTime));
|
||||
System.out.println();
|
||||
System.out.println("Tests Pass Error Fail Ignore Seconds");
|
||||
}
|
||||
}
|
||||
|
||||
private void reportTestRun() {
|
||||
int successes = testsTotal - errorsTotal - failuresTotal - ignoresTotal;
|
||||
|
||||
if (reportLevel != BRIEF) {
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
System.out.format(
|
||||
"Tests Pass Error Fail Ignore Seconds TOTAL (%d classes)\n",
|
||||
classCount);
|
||||
System.out.format(" %4d %4d %4d %4d %4d %6s\n", testsTotal,
|
||||
successes, errorsTotal, failuresTotal, ignoresTotal,
|
||||
elapsed(overallStartTime));
|
||||
|
||||
if (reportLevel != BRIEF) {
|
||||
System.out.println("Ending test run at "
|
||||
+ time(System.currentTimeMillis()));
|
||||
}
|
||||
}
|
||||
|
||||
private void openCurrentClass(Description description) {
|
||||
currentClass = description.getTestClass();
|
||||
classStartTime = System.currentTimeMillis();
|
||||
testsCurrentClass = 0;
|
||||
errorsCurrentClass = 0;
|
||||
failuresCurrentClass = 0;
|
||||
ignoresCurrentClass = 0;
|
||||
}
|
||||
|
||||
private void closeCurrentClass() {
|
||||
classCount++;
|
||||
testsTotal += testsCurrentClass;
|
||||
errorsTotal += errorsCurrentClass;
|
||||
failuresTotal += failuresCurrentClass;
|
||||
ignoresTotal += ignoresCurrentClass;
|
||||
}
|
||||
|
||||
private void reportCurrentClassStart() {
|
||||
if (reportLevel == FULL) {
|
||||
System.out.format("Tests Pass Error Fail Ignore Seconds %s\n",
|
||||
currentClass.getName());
|
||||
}
|
||||
}
|
||||
|
||||
private void reportCurrentClass() {
|
||||
int successes = testsCurrentClass - errorsCurrentClass
|
||||
- failuresCurrentClass - ignoresCurrentClass;
|
||||
if (reportLevel == MORE) {
|
||||
System.out.format(" %4d %4d %4d %4d %4d %6s %s\n",
|
||||
testsCurrentClass, successes, errorsCurrentClass,
|
||||
failuresCurrentClass, ignoresCurrentClass,
|
||||
elapsed(classStartTime), currentClass.getSimpleName());
|
||||
}
|
||||
if (reportLevel == FULL) {
|
||||
System.out.println("-----------------------------------");
|
||||
System.out.format(" %4d %4d %4d %4d %4d %6s\n",
|
||||
testsCurrentClass, successes, errorsCurrentClass,
|
||||
failuresCurrentClass, ignoresCurrentClass,
|
||||
elapsed(classStartTime));
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
|
||||
private void openCurrentTest(Description description) {
|
||||
currentTest = description.getMethodName();
|
||||
testHadError = false;
|
||||
testFailed = false;
|
||||
testIgnored = false;
|
||||
testStartTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
private void closeCurrentTest() {
|
||||
if (testHadError) {
|
||||
errorsCurrentClass++;
|
||||
}
|
||||
if (testFailed) {
|
||||
failuresCurrentClass++;
|
||||
}
|
||||
if (testIgnored) {
|
||||
ignoresCurrentClass++;
|
||||
}
|
||||
testsCurrentClass++;
|
||||
}
|
||||
|
||||
private boolean isError(Failure failure) {
|
||||
Throwable throwable = failure.getException();
|
||||
return (throwable != null) && !(throwable instanceof AssertionError);
|
||||
}
|
||||
|
||||
private void reportError(Failure error) {
|
||||
Description description = error.getDescription();
|
||||
String methodName = description.getMethodName();
|
||||
String className = description.getTestClass().getName();
|
||||
String message = error.getMessage();
|
||||
System.out.format("EXCEPTION: test %s() in %s: %s\n",
|
||||
methodName, className, message);
|
||||
System.out.println(formatStackTrace(error.getException()));
|
||||
}
|
||||
|
||||
private void reportFailure(Failure failure) {
|
||||
Description description = failure.getDescription();
|
||||
String methodName = description.getMethodName();
|
||||
String className = description.getTestClass().getName();
|
||||
String message = failure.getMessage();
|
||||
System.out.format("TEST FAILED: test %s() in %s: %s\n", methodName,
|
||||
className, message);
|
||||
}
|
||||
|
||||
private void reportCurrentTest() {
|
||||
if (reportLevel == FULL) {
|
||||
char passFlag = (testIgnored | testFailed | testHadError) ? ' '
|
||||
: '1';
|
||||
char errorFlag = testHadError ? '1' : ' ';
|
||||
char failFlag = testFailed ? '1' : ' ';
|
||||
char ignoreFlag = testIgnored ? '1' : ' ';
|
||||
System.out.format(
|
||||
" %c %c %c %c %6s %s()\n",
|
||||
passFlag, errorFlag, failFlag, ignoreFlag,
|
||||
elapsed(testStartTime), currentTest);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Formatting methods.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private final SimpleDateFormat formatter = new SimpleDateFormat(
|
||||
"HH:mm:ss 'on' MMM dd, yyyy");
|
||||
|
||||
private String time(long time) {
|
||||
return formatter.format(new Date(time));
|
||||
}
|
||||
|
||||
/** Show elapsed time in 6 columns. */
|
||||
private String elapsed(long start) {
|
||||
long interval = System.currentTimeMillis() - start;
|
||||
return String.format("%6.2f", ((float) interval) / 1000.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim the stack trace: don't show the line saying "23 more", and don't
|
||||
* show the lines about org.junit or java.lang.reflect or sun.reflect.
|
||||
*
|
||||
* Once we hit some "client code", we won't trim any futher lines even if
|
||||
* they belong to org.junit, or the others.
|
||||
*
|
||||
* If we have nested exceptions, the process repeats for each "Caused by"
|
||||
* section.
|
||||
*/
|
||||
private String formatStackTrace(Throwable throwable) {
|
||||
StringWriter w = new StringWriter();
|
||||
throwable.printStackTrace(new PrintWriter(w));
|
||||
String[] lineArray = w.toString().split("\\n");
|
||||
List<String> lines = new ArrayList<String>(Arrays.asList(lineArray));
|
||||
|
||||
boolean removing = true;
|
||||
for (int i = lines.size() - 1; i > 0; i--) {
|
||||
String line = lines.get(i);
|
||||
if (removing) {
|
||||
if (line.matches("\\s*[\\.\\s\\d]+more\\s*")
|
||||
|| line.contains("at "
|
||||
+ VitroTestRunner.class.getName())
|
||||
|| line.contains("at org.junit.")
|
||||
|| line.contains("at java.lang.reflect.")
|
||||
|| line.contains("at sun.reflect.")) {
|
||||
lines.remove(line);
|
||||
} else {
|
||||
removing = false;
|
||||
}
|
||||
} else {
|
||||
if (line.contains("Caused by: ")) {
|
||||
removing = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (String line : lines) {
|
||||
result.append(line).append('\n');
|
||||
}
|
||||
return result.toString().trim();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||
|
||||
package edu.cornell.mannlib.vitro.utilities.testing;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.junit.runner.JUnitCore;
|
||||
|
||||
/**
|
||||
* A Java application that will run the Vitro unit tests. It searches for unit
|
||||
* tests in the supplied source directory (any file whose name is *Test.Java).
|
||||
* It runs the tests with a variety of reporting detail, depending on the level
|
||||
* selected. If the level selector is absent or unrecognized, the medium level
|
||||
* is used.
|
||||
*
|
||||
* @author jeb228
|
||||
*/
|
||||
public class VitroTestRunner {
|
||||
public enum ReportLevel {
|
||||
/** Report only the one-line summary. */
|
||||
BRIEF,
|
||||
/** Report times and statistics for each test class. */
|
||||
MORE,
|
||||
/** Report times and statistics for each test method. */
|
||||
FULL
|
||||
}
|
||||
|
||||
private final List<Class<?>> classes;
|
||||
private final VitroTestRunListener listener;
|
||||
|
||||
/**
|
||||
* Locate the test classes. Initialize the test listener.
|
||||
*/
|
||||
public VitroTestRunner(File sourceRootDir, ReportLevel reportLevel) {
|
||||
List<String> classNames = getListOfTestClassNames(sourceRootDir);
|
||||
this.classes = getTestClasses(classNames);
|
||||
this.listener = new VitroTestRunListener(reportLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a recursive search through the source directory.
|
||||
*/
|
||||
private List<String> getListOfTestClassNames(File sourceRootDir) {
|
||||
SortedSet<String> names = new TreeSet<String>();
|
||||
searchForTestClasses(names, "", sourceRootDir);
|
||||
return new ArrayList<String>(names);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively search the directory for files in the form "*Test.java".
|
||||
* Ignore any files or directories whose names start with a "."
|
||||
*/
|
||||
private void searchForTestClasses(SortedSet<String> names, String prefix,
|
||||
File directory) {
|
||||
for (File file : directory.listFiles()) {
|
||||
String filename = file.getName();
|
||||
if (filename.startsWith(".")) {
|
||||
// ignore .svn, etc.
|
||||
} else if (file.isDirectory()) {
|
||||
searchForTestClasses(names, prefix + filename + ".", file);
|
||||
} else if (filename.endsWith("Test.java")) {
|
||||
String classname = filename.substring(0, filename.length() - 5);
|
||||
names.add(prefix + classname);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a class for each test class name.
|
||||
*/
|
||||
private List<Class<?>> getTestClasses(List<String> classNames) {
|
||||
List<Class<?>> classes = new ArrayList<Class<?>>();
|
||||
for (String classname : classNames) {
|
||||
try {
|
||||
classes.add(Class.forName(classname));
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new IllegalArgumentException("Can't load test class: "
|
||||
+ classname, e);
|
||||
}
|
||||
}
|
||||
return classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* We've located all of the test clases. Now run them.
|
||||
*/
|
||||
private void run() {
|
||||
JUnitCore junitCore = new JUnitCore();
|
||||
junitCore.addListener(this.listener);
|
||||
junitCore.run(this.classes.toArray(new Class<?>[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Did any of the tests fail?
|
||||
*/
|
||||
private boolean didEverythingPass() {
|
||||
return this.listener.didEverythingPass();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* You must provide a path to the source directory of the test classes.
|
||||
* </p>
|
||||
* <p>
|
||||
* You may also provide a reporting level of "BRIEF", "MORE", or "FULL". If
|
||||
* no level is provided, or if it is not recognized, "BRIEF" is used.
|
||||
* </p>
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
if ((args.length < 1) || (args.length > 2)) {
|
||||
usage("Wrong number of arguments: expecting 1 or 2, but found "
|
||||
+ args.length + ".");
|
||||
}
|
||||
File sourceRootDir = new File(args[0]);
|
||||
|
||||
if (!sourceRootDir.exists()) {
|
||||
usage(sourceRootDir + " does not exist.");
|
||||
}
|
||||
if (!sourceRootDir.isDirectory()) {
|
||||
usage(sourceRootDir + " is not a directory.");
|
||||
}
|
||||
|
||||
ReportLevel reportLevel = ReportLevel.MORE;
|
||||
if (args.length == 2) {
|
||||
for (ReportLevel level : ReportLevel.values()) {
|
||||
if (level.toString().equalsIgnoreCase(args[1])) {
|
||||
reportLevel = level;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VitroTestRunner runner = new VitroTestRunner(sourceRootDir, reportLevel);
|
||||
runner.run();
|
||||
|
||||
if (!runner.didEverythingPass()) {
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell them how it should have been done.
|
||||
*/
|
||||
private static void usage(String message) {
|
||||
System.out.println(message);
|
||||
System.out.println("usage: " + VitroTestRunner.class.getSimpleName()
|
||||
+ " sourceRootDirectory [ BRIEF | MORE | FULL ]");
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue