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,192 @@
=begin
--------------------------------------------------------------------------------
This script will try to show that the JARs in a list are not needed for VIVO
(or Vitro).
Given a list of JARs, it will determine what packages the JAR contains, and will
search the source files of the project for references to those packages.
The list of JARs should be created by running the jarlist target of the build file.
That will use JarJar to create a list of JAR file that are not directly referenced
by the classes in the build. But what about JARs that are needed by a call using
reflection? Like Class.forName("this.that.theOther"). We know this is how the MySQL
driver is loaded (so we need mysql-connector-java-5.1.16-bin.jar). Are there any
others? This script will try to find them.
The jarlist target includes a list of JARs that we know are needed, such as the MySQL
connector. If this script finds any JARs that are needed, they should be added to that
list, and the target run again, in case they depend on other JARs in turn.
--------------------------------------------------------------------------------
One of the tricks that this script uses is to prune the list of packages to
search for. If a JAR contains both "this.that.other" and "this.that", then we
only need to search for "this.that", since it will reveal uses of "this.that.other"
as well.
--------------------------------------------------------------------------------
pass the name of the file that contains the JAR names
pass the root directory of the combined vitro/vivo distro
(appbase)
--------------------------------------------------------------------------------
Search all of the *.java, *.jsp, *.js, *.tld, *.xml files for mention of these package names
For each hit, print the file path, line number, the package name and the JAR name.
To search files:
find -X . -name \*.db -or -name \*pot | xargs grep 'org\.package\.name'
grep -H -n -f file_with_package_patterns
or can we do this all with grep on each string?
grep -r --include=*.jsp
grep -H -n -r --include=*.javaxd --include=*.jsp org\.package\.name .
and precede the output with a header that lists the package
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
=end
# Just say what we're doing.
#
def print_args
puts "Scanning for JAR usage."
puts "Base directory is #{@scan_base_directory}"
puts "JAR list is in #{@jar_names_file}"
puts
end
# Build a Hash of JAR names mapped to (reduced) arrays of package names.
#
def figure_package_names_from_jars
@packages_for_jars = {}
File.open(@jar_names_file) do |file|
file.each do |line|
jar_name = line.strip
@packages_for_jars[jar_name] = figure_package_names_for_jar(jar_name)
end
end
end
# Figure out the reduced set of package names for this JAR
#
def figure_package_names_for_jar(jar_name)
jar_path = "#{@scan_base_directory}/lib/#{jar_name}"
jar_contents = `jar -tf '#{jar_path}'`
packages = analyze_jar_contents(jar_contents)
reduced = reduce_package_names(packages)
end
# Ask the JAR what it contains. Keep only the names of populated packages.
# Ignore packages that are not at least 2 levels deep.
#
def analyze_jar_contents(jar_contents)
packages = []
jar_contents.lines do |line|
line.strip!
if line.end_with?('/')
elsif line.start_with?('META-INF')
elsif line.count('/') < 2
else
package = line[0...line.rindex('/')].tr('/', '.')
packages << package
end
end
packages.uniq.sort!
end
# Remove the names of any sub-packages. Searching for the parent package will be sufficient.
#
def reduce_package_names(packages)
reduced = []
packages.each do |candidate|
redundant = FALSE
reduced.each do |result|
redundant = TRUE if candidate.start_with?(result)
end
reduced << candidate unless redundant
end
reduced
end
# Show what packages we will search for, and for which JAR
#
def print_package_names_for_jars
puts "Packages for each jar"
@packages_for_jars.each do |jar_name, package_array|
puts " #{jar_name}"
package_array.each do |package_name|
puts " #{package_name}"
end
puts
end
end
#
#
def show_packages_in_source_files
@packages_for_jars.each do |jar_name, package_array|
show_packages_for_jar_in_source_files(jar_name, package_array)
end
end
#
#
def show_packages_for_jar_in_source_files(jar_name, package_array)
puts "------------------------------- #{jar_name} ------------------------------"
package_array.each do |package_name|
show_package_in_source_files(package_name)
end
end
#
#
def show_package_in_source_files(package_name)
puts "#{package_name}"
include_parms = build_include_parms(["*.java", "*.jsp", "*.xml", "*.tld", "*.js" ])
package_name_pattern = package_name.sub(/\./, "\\.")
system "grep -H -n -r #{include_parms} #{package_name_pattern} '#{@scan_base_directory}'"
puts
end
#
#
def build_include_parms(file_specs)
"--include=" + file_specs.join(" --include=")
end
#
#
# ------------------------------------------------------------------------------------
# Standalone calling.
#
# Do this if this program was called from the command line. That is, if the command
# expands to the path of this file.
# ------------------------------------------------------------------------------------
#
if File.expand_path($0) == File.expand_path(__FILE__)
if ARGV.length != 2
raise("usage is: ruby jarUsageScanner.rb <jar_names_file> <scan_base_directory>")
end
@jar_names_file, @scan_base_directory = ARGV
if !File.file?(@jar_names_file)
raise "File does not exist: '#{@jar_names_file}'."
end
if !File.directory?(@scan_base_directory)
raise "Directory does not exist: '#{@scan_base_directory}'."
end
print_args
figure_package_names_from_jars
print_package_names_for_jars
show_packages_in_source_files
end

Binary file not shown.

Binary file not shown.

Binary file not shown.

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