2 * Copyright (C) 2014, United States Government, as represented by the
3 * Administrator of the National Aeronautics and Space Administration.
6 * The Java Pathfinder core (jpf-core) platform is licensed under the
7 * Apache License, Version 2.0 (the "License"); you may not use this file except
8 * in compliance with the License. You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0.
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
19 package gov.nasa.jpf.tool;
21 import gov.nasa.jpf.Config;
22 import gov.nasa.jpf.JPFClassLoader;
23 import gov.nasa.jpf.util.FileUtils;
24 import gov.nasa.jpf.util.JPFSiteUtils;
26 import java.lang.reflect.Field;
28 import java.lang.reflect.InvocationTargetException;
29 import java.lang.reflect.Method;
30 import java.lang.reflect.Modifier;
31 import java.util.ArrayList;
32 import java.util.List;
35 * tool to run JPF test with configured classpath
37 * arguments are supposed to be of type
39 * {<config-option>} <JPF-test-class> {<test-method>}
41 * all leading config options are used to create the initial Config, but be
42 * aware of that each test (TestJPF.verifyX() invocation) uses its own
43 * Config and JPF object, i.e. can have different path settings
45 * This automatically adds <project>.test_classpath to the startup classpath
47 public class RunTest extends Run {
49 public static final int HELP = 0x1;
50 public static final int SHOW = 0x2;
51 public static final int LOG = 0x4;
52 public static final int QUIET = 0x8;
54 static final String TESTJPF_CLS = "gov.nasa.jpf.util.test.TestJPF";
58 public static Config getConfig(){
62 public static class Failed extends RuntimeException {
67 public static int getOptions (String[] args){
72 for (int i = 0; i < args.length; i++) {
74 if ("-help".equals(a)){
78 } else if ("-show".equals(a)) {
82 } else if ("-log".equals(a)){
86 } else if ("-quiet".equals(a)){
96 public static boolean isOptionEnabled (int option, int mask){
97 return ((mask & option) != 0);
100 public static void showUsage() {
101 System.out.println("Usage: \"java [<vm-option>..] -jar ...RunTest.jar [<jpf-option>..] [<class> [<app-arg>..]]");
102 System.out.println(" <jpf-option> : -help : print usage information and exit");
103 System.out.println(" | -log : print configuration initialization steps");
104 System.out.println(" | -show : print configuration dictionary contents");
105 System.out.println(" | -quiet : don't show System.out test output");
106 System.out.println(" | +<key>=<value> : add or override <key>/<value> pair to global config");
107 System.out.println(" | +test.<key>=<value> : add or override <key>/<value> pair in test config");
108 System.out.println(" <class> : application class name");
109 System.out.println(" <methods> : test methods of application class");
112 public static void main(String[] args) {
113 int options = getOptions( args);
115 if (isOptionEnabled(HELP, options)) {
120 if (isOptionEnabled(LOG, options)) {
121 Config.enableLogging(true);
124 config = new Config(args);
126 if (isOptionEnabled(SHOW, options)) {
127 config.printEntries();
130 args = removeConfigArgs( args);
131 String testClsName = getTestClassName(args);
132 String[] testArgs = getTestArgs(args);
134 String[] testPathElements = getTestPathElements(config);
135 JPFClassLoader cl = config.initClassLoader(RunTest.class.getClassLoader());
136 addTestClassPath( cl, testPathElements);
138 Class<?> testJpfCls = null;
140 testJpfCls = cl.loadClass( TESTJPF_CLS);
142 if (isOptionEnabled(QUIET, options)){
143 Field f = testJpfCls.getDeclaredField("quiet");
144 f.setAccessible(true);
145 f.setBoolean( null, true);
148 } catch (NoClassDefFoundError ncfx) {
149 error("class did not resolve: " + ncfx.getMessage());
151 } catch (ClassNotFoundException cnfx) {
152 error("class not found " + cnfx.getMessage() + ", check native_classpath in jpf.properties");
155 // we let these pass for now since it only means the quiet option is not going to work
156 } catch (NoSuchFieldException ex) {
157 warning("incompatible " + TESTJPF_CLS + " version, quiet mode will not work");
158 } catch (IllegalAccessException ex) {
159 warning("incompatible " + TESTJPF_CLS + " version, quiet mode will not work");
162 // <2do> refactor - each test class should be (optionally) loaded through a new ClassLoader instance
163 // to make sure tests don't have static field carry-over
165 List<Class<?>> testClasses = getTestClasses(cl, testJpfCls, testPathElements, testClsName);
166 if (testClasses.isEmpty()){
167 System.out.println("no test classes found");
174 for (Class<?> testCls : testClasses){
179 try { // check if there is a main(String[]) method
180 Method mainEntry = testCls.getDeclaredMethod("main", String[].class);
181 mainEntry.invoke(null, (Object) testArgs);
183 } catch (NoSuchMethodException x) { // no main(String[]), call TestJPF.runTests(testCls,args) directly
184 Method mainEntry = testJpfCls.getDeclaredMethod("runTests", Class.class, String[].class);
185 mainEntry.invoke(null, new Object[]{testCls, testArgs});
190 } catch (NoSuchMethodException x) {
191 error("no suitable main() or runTests() in " + testCls.getName());
192 } catch (IllegalAccessException iax) {
193 error(iax.getMessage());
196 } catch (NoClassDefFoundError ncfx) {
197 error("class did not resolve: " + ncfx.getMessage());
198 } catch (InvocationTargetException ix) {
199 Throwable cause = ix.getCause();
200 if (cause instanceof Failed){
201 // no need to report - the test did run and reported why it failed
204 error(ix.getCause().getMessage());
209 System.out.println();
210 System.out.printf("tested classes: %d, passed: %d\n", nTested, nPass);
213 static Class<?> loadTestClass (JPFClassLoader cl, Class<?> testJpfCls, String testClsName){
215 Class<?> testCls = cl.loadClass(testClsName);
216 if (testJpfCls.isAssignableFrom(testCls)){
217 if (!Modifier.isAbstract(testCls.getModifiers())){
224 } catch (NoClassDefFoundError ncfx) {
225 error("class did not resolve: " + ncfx.getMessage());
228 } catch (ClassNotFoundException cnfx) {
229 error("class not found " + cnfx.getMessage() + ", check test_classpath in jpf.properties");
234 static boolean hasWildcard (String pattern){
235 return (pattern.indexOf('*') >= 0);
238 static List<Class<?>> getTestClasses (JPFClassLoader cl, Class<?> testJpfCls, String[] testPathElements, String testClsPattern ){
239 List<Class<?>> testClasses = new ArrayList<Class<?>>();
241 if (testClsPattern.startsWith(".")){
242 testClsPattern = "gov.nasa.jpf" + testClsPattern;
245 if (!hasWildcard(testClsPattern)){ // that's simple, no need to look into dirs
246 Class<?> testCls = loadTestClass( cl, testJpfCls, testClsPattern);
247 if (testCls == null){ // error if this was an explicit classname
248 error ("specified class name not found or no TestJPF derived class: " + testClsPattern);
250 testClasses.add(testCls);
252 } else { // we have to recursively look into the testPathElements for potential test classes
253 List<String> classFileList = getClassFileList( testPathElements, testClsPattern);
255 for (String candidate : classFileList){
256 Class<?> testCls = loadTestClass( cl, testJpfCls, candidate);
257 if (testCls != null){
258 testClasses.add(testCls);
266 static void collectMatchingFiles (int nPrefix, File dir, List<String> list, String pattern){
267 for (File e : dir.listFiles()){
268 if (e.isDirectory()){
269 collectMatchingFiles(nPrefix, e, list, pattern);
271 } else if (e.isFile()){
272 String pn = e.getPath().substring(nPrefix);
273 if (pn.matches(pattern)){
274 String clsName = pn.substring(0, pn.length() - 6); // strip cp entry and ".class"
275 clsName = clsName.replace( File.separatorChar, '.');
282 static List<String> getClassFileList (String[] testPathElements, String testClsPattern){
283 List<String> list = new ArrayList<String>();
284 String tcp = testClsPattern.replace('.', File.separatorChar);
285 tcp = tcp.replace("*", ".*") + "\\.class";
287 for (String tpe : testPathElements){
288 File tp = new File(tpe);
289 int nPrefix = tp.getPath().length()+1;
290 collectMatchingFiles( nPrefix, tp, list, tcp);
296 static boolean isPublicStatic (Method m){
297 int mod = m.getModifiers();
298 return ((mod & (Modifier.PUBLIC | Modifier.STATIC)) == (Modifier.PUBLIC | Modifier.STATIC));
301 static String[] getTestPathElements (Config conf){
302 String projectId = JPFSiteUtils.getCurrentProjectId();
304 if (projectId != null) {
305 String testCpKey = projectId + ".test_classpath";
306 return config.getCompactTrimmedStringArray(testCpKey);
309 return new String[0];
313 static void addTestClassPath (JPFClassLoader cl, String[] testPathElements){
314 if (testPathElements != null) {
315 for (String pe : testPathElements) {
317 cl.addURL(FileUtils.getURL(pe));
318 } catch (Throwable x) {
319 error("malformed test_classpath URL: " + pe);
325 static boolean isOptionArg(String a){
326 if (a != null && !a.isEmpty()){
327 char c = a.charAt(0);
328 if ((c == '+') || (c == '-')){
335 static String getTestClassName(String[] args){
336 for (int i=0; i<args.length; i++){
338 if (a != null && !isOptionArg(a)){
346 // return everything after the first free arg
347 static String[] getTestArgs(String[] args){
350 for (i=0; i<args.length; i++){
352 if (a != null && !isOptionArg(a)){
357 if (i >= args.length-1){
358 return new String[0];
360 String[] testArgs = new String[args.length-i-1];
361 System.arraycopy(args,i+1, testArgs, 0, testArgs.length);