//
// Copyright (C) 2010 United States Government as represented by the
// Administrator of the National Aeronautics and Space Administration
// (NASA). All Rights Reserved.
//
// This software is distributed under the NASA Open Source Agreement
// (NOSA), version 1.3. The NOSA has been approved by the Open Source
// Initiative. See the file NOSA-1.3-JPF at the top of the distribution
// directory tree for the complete NOSA document.
//
// THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY
// KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT
// LIMITED TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO
// SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR
// A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT
// THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT
// DOCUMENTATION, IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE.
//
package gov.nasa.jpf.tool;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import gov.nasa.jpf.Config;
import gov.nasa.jpf.JPFClassLoader;
import gov.nasa.jpf.util.FileUtils;
import gov.nasa.jpf.util.JPFSiteUtils;
/**
* tool to run JPF test with configured classpath
*
* arguments are supposed to be of type
*
* {<config-option>} <JPF-test-class> {<test-method>}
*
* all leading config options are used to create the initial Config, but be
* aware of that each test (TestJPF.verifyX() invocation) uses its own
* Config and JPF object, i.e. can have different path settings
*
* This automatically adds <project>.test_classpath to the startup classpath
*/
public class RunTest extends Run {
public static final int HELP = 0x1;
public static final int SHOW = 0x2;
public static final int LOG = 0x4;
public static final int QUIET = 0x8;
static final String TESTJPF_CLS = "gov.nasa.jpf.util.test.TestJPF";
static Config config;
public static Config getConfig(){
return config;
}
public static class Failed extends RuntimeException {
public Failed (){
}
}
public static int getOptions (String[] args){
int mask = 0;
if (args != null){
for (int i = 0; i < args.length; i++) {
String a = args[i];
if ("-help".equals(a)){
args[i] = null;
mask |= HELP;
} else if ("-show".equals(a)) {
args[i] = null;
mask |= SHOW;
} else if ("-log".equals(a)){
args[i] = null;
mask |= LOG;
} else if ("-quiet".equals(a)){
args[i] = null;
mask |= QUIET;
}
}
}
return mask;
}
public static boolean isOptionEnabled (int option, int mask){
return ((mask & option) != 0);
}
public static void showUsage() {
System.out.println("Usage: \"java [<vm-option>..] -jar ...RunTest.jar [<jpf-option>..] [<class> [<app-arg>..]]");
System.out.println(" <jpf-option> : -help : print usage information and exit");
System.out.println(" | -log : print configuration initialization steps");
System.out.println(" | -show : print configuration dictionary contents");
System.out.println(" | -quiet : don't show System.out test output");
System.out.println(" | +<key>=<value> : add or override <key>/<value> pair to global config");
System.out.println(" | +test.<key>=<value> : add or override <key>/<value> pair in test config");
System.out.println(" <class> : application class name");
System.out.println(" <methods> : test methods of application class");
}
public static void main(String[] args) {
int options = getOptions( args);
if (isOptionEnabled(HELP, options)) {
showUsage();
return;
}
if (isOptionEnabled(LOG, options)) {
Config.enableLogging(true);
}
config = new Config(args);
if (isOptionEnabled(SHOW, options)) {
config.printEntries();
}
args = removeConfigArgs( args);
String testClsName = getTestClassName(args);
String[] testArgs = getTestArgs(args);
String[] testPathElements = getTestPathElements(config);
JPFClassLoader cl = config.initClassLoader(RunTest.class.getClassLoader());
addTestClassPath( cl, testPathElements);
Class<?> testJpfCls = null;
try {
testJpfCls = cl.loadClass( TESTJPF_CLS);
if (isOptionEnabled(QUIET, options)){
Field f = testJpfCls.getDeclaredField("quiet");
f.setAccessible(true);
f.setBoolean( null, true);
}
} catch (NoClassDefFoundError ncfx) {
error("class did not resolve: " + ncfx.getMessage());
return;
} catch (ClassNotFoundException cnfx) {
error("class not found " + cnfx.getMessage() + ", check native_classpath in jpf.properties");
return;
// we let pass this for now since it only means the quiet option is not going to work
} catch (NoSuchFieldException ex) {
} catch (IllegalAccessException ex) {
}
List<Class<?>> testClasses = getTestClasses(cl, testJpfCls, testPathElements, testClsName);
if (testClasses.isEmpty()){
System.out.println("no test classes found");
return;
}
int nTested = 0;
int nPass = 0;
for (Class<?> testCls : testClasses){
nTested++;
try {
try {
try { // check if there is a main(String[]) method
Method mainEntry = testCls.getDeclaredMethod("main", String[].class);
mainEntry.invoke(null, (Object) testArgs);
} catch (NoSuchMethodException x) { // no main(String[]), call TestJPF.runTests(testCls,args) directly
Method mainEntry = testJpfCls.getDeclaredMethod("runTests", Class.class, String[].class);
mainEntry.invoke(null, new Object[]{testCls, testArgs});
}
nPass++;
} catch (NoSuchMethodException x) {
error("no suitable main() or runTests() in " + testCls.getName());
} catch (IllegalAccessException iax) {
error(iax.getMessage());
}
} catch (NoClassDefFoundError ncfx) {
error("class did not resolve: " + ncfx.getMessage());
} catch (InvocationTargetException ix) {
Throwable cause = ix.getCause();
if (cause instanceof Failed){
// no need to report - the test did run and reported why it failed
System.exit(1);
} else {
error(ix.getCause().getMessage());
}
}
}
System.out.println();
System.out.printf("tested classes: %d, passed: %d\n", nTested, nPass);
}
static Class<?> loadTestClass (JPFClassLoader cl, Class<?> testJpfCls, String testClsName){
try {
Class<?> testCls = cl.loadClass(testClsName);
if (testJpfCls.isAssignableFrom(testCls)){
if (!Modifier.isAbstract(testCls.getModifiers())){
return testCls;
}
}
return null;
} catch (NoClassDefFoundError ncfx) {
error("class did not resolve: " + ncfx.getMessage());
return null;
} catch (ClassNotFoundException cnfx) {
error("class not found " + cnfx.getMessage() + ", check test_classpath in jpf.properties");
return null;
}
}
static boolean hasWildcard (String pattern){
return (pattern.indexOf('*') >= 0);
}
static List<Class<?>> getTestClasses (JPFClassLoader cl, Class<?> testJpfCls, String[] testPathElements, String testClsName ){
List<Class<?>> testClasses = new ArrayList<Class<?>>();
if (!hasWildcard(testClsName)){ // that's simple, no need to look into dirs
Class<?> testCls = loadTestClass( cl, testJpfCls, testClsName);
if (testCls == null){ // error if this was an explicit classname
error ("specified class name not found or no TestJPF derived class: " + testClsName);
}
testClasses.add(testCls);
} else { // we have to recursively look into the testPathElements for potential test classes
List<String> classFileList = getClassFileList( testPathElements, testClsName);
for (String candidate : classFileList){
Class<?> testCls = loadTestClass( cl, testJpfCls, candidate);
if (testCls != null){
testClasses.add(testCls);
}
}
}
return testClasses;
}
static void collectMatchingFiles (int nPrefix, File dir, List<String> list, String pattern){
for (File e : dir.listFiles()){
if (e.isDirectory()){
collectMatchingFiles(nPrefix, e, list, pattern);
} else if (e.isFile()){
String pn = e.getPath().substring(nPrefix);
if (pn.matches(pattern)){
String clsName = pn.substring(0, pn.length() - 6); // strip cp entry and ".class"
clsName = clsName.replace( File.separatorChar, '.');
list.add(clsName);
}
}
}
}
static List<String> getClassFileList (String[] testPathElements, String testClsPattern){
List<String> list = new ArrayList<String>();
String tcp = testClsPattern.replace('.', File.separatorChar);
tcp = tcp.replace("*", ".*") + "\\.class";
for (String tpe : testPathElements){
File tp = new File(tpe);
int nPrefix = tp.getPath().length()+1;
collectMatchingFiles( nPrefix, tp, list, tcp);
}
return list;
}
static boolean isPublicStatic (Method m){
int mod = m.getModifiers();
return ((mod & (Modifier.PUBLIC | Modifier.STATIC)) == (Modifier.PUBLIC | Modifier.STATIC));
}
static String[] getTestPathElements (Config conf){
String projectId = JPFSiteUtils.getCurrentProjectId();
if (projectId != null) {
String testCpKey = projectId + ".test_classpath";
return config.getCompactTrimmedStringArray(testCpKey);
} else {
return new String[0];
}
}
static void addTestClassPath (JPFClassLoader cl, String[] testPathElements){
if (testPathElements != null) {
for (String pe : testPathElements) {
try {
cl.addURL(FileUtils.getURL(pe));
} catch (Throwable x) {
error("malformed test_classpath URL: " + pe);
}
}
}
}
static boolean isOptionArg(String a){
if (a != null && !a.isEmpty()){
char c = a.charAt(0);
if ((c == '+') || (c == '-')){
return true;
}
}
return false;
}
static String getTestClassName(String[] args){
for (int i=0; i<args.length; i++){
String a = args[i];
if (a != null && !isOptionArg(a)){
return a;
}
}
return null;
}
// return everything after the first free arg
static String[] getTestArgs(String[] args){
int i;
for (i=0; i<args.length; i++){
String a = args[i];
if (a != null && !isOptionArg(a)){
break;
}
}
if (i >= args.length-1){
return new String[0];
} else {
String[] testArgs = new String[args.length-i-1];
System.arraycopy(args,i+1, testArgs, 0, testArgs.length);
return testArgs;
}
}
}