Maven migration (first draft)

This commit is contained in:
Graham Triggs 2015-11-19 23:47:12 +00:00
parent 5e0329908c
commit e1ff94ccaf
2866 changed files with 1112 additions and 616 deletions

View file

@ -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();
}
}
}

View file

@ -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();
}
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}