Fixes default method resolution (#159)
[jpf-core.git] / src / main / gov / nasa / jpf / tool / RunTest.java
1 /*
2  * Copyright (C) 2014, United States Government, as represented by the
3  * Administrator of the National Aeronautics and Space Administration.
4  * All rights reserved.
5  *
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
9  * 
10  *        http://www.apache.org/licenses/LICENSE-2.0. 
11  *
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.
17  */
18
19 package gov.nasa.jpf.tool;
20
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;
25 import java.io.File;
26 import java.lang.reflect.Field;
27
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;
33
34 /**
35  * tool to run JPF test with configured classpath
36  *
37  * arguments are supposed to be of type
38  *
39  *   {<config-option>} <JPF-test-class> {<test-method>}
40  *
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
44  *
45  * This automatically adds <project>.test_classpath to the startup classpath
46  */
47 public class RunTest extends Run {
48
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;
53
54   static final String TESTJPF_CLS = "gov.nasa.jpf.util.test.TestJPF";
55   
56   static Config config;
57
58   public static Config getConfig(){
59     return config;
60   }
61
62   public static class Failed extends RuntimeException {
63     public Failed (){
64     }
65   }
66
67   public static int getOptions (String[] args){
68     int mask = 0;
69
70     if (args != null){
71
72       for (int i = 0; i < args.length; i++) {
73         String a = args[i];
74         if ("-help".equals(a)){
75           args[i] = null;
76           mask |= HELP;
77
78         } else if ("-show".equals(a)) {
79           args[i] = null;
80           mask |= SHOW;
81
82         } else if ("-log".equals(a)){
83           args[i] = null;
84           mask |= LOG;
85
86         } else if ("-quiet".equals(a)){
87           args[i] = null;
88           mask |= QUIET;
89         }
90       }
91     }
92
93     return mask;
94   }
95
96   public static boolean isOptionEnabled (int option, int mask){
97     return ((mask & option) != 0);
98   }
99
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");
110   }
111   
112   public static void main(String[] args) {
113     int options = getOptions( args);
114     
115     if (isOptionEnabled(HELP, options)) {
116       showUsage();
117       return;
118     }
119
120     if (isOptionEnabled(LOG, options)) {
121       Config.enableLogging(true);
122     }
123
124     config = new Config(args);
125
126     if (isOptionEnabled(SHOW, options)) {
127       config.printEntries();
128     }
129     
130     args = removeConfigArgs( args);
131     String testClsName = getTestClassName(args);
132     String[] testArgs = getTestArgs(args);
133     
134     String[] testPathElements = getTestPathElements(config);
135     JPFClassLoader cl = config.initClassLoader(RunTest.class.getClassLoader());
136     addTestClassPath( cl, testPathElements);
137
138     Class<?> testJpfCls = null;
139     try {
140       testJpfCls = cl.loadClass( TESTJPF_CLS);
141       
142       if (isOptionEnabled(QUIET, options)){
143         Field f = testJpfCls.getDeclaredField("quiet");
144         f.setAccessible(true);
145         f.setBoolean( null, true);
146       }
147       
148     } catch (NoClassDefFoundError ncfx) {
149       error("class did not resolve: " + ncfx.getMessage());
150       return;
151     } catch (ClassNotFoundException cnfx) {
152       error("class not found " + cnfx.getMessage() + ", check native_classpath in jpf.properties");
153       return;
154       
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");
160     }
161     
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
164     
165     List<Class<?>> testClasses = getTestClasses(cl, testJpfCls, testPathElements, testClsName);
166     if (testClasses.isEmpty()){
167       System.out.println("no test classes found");
168       return;
169     }
170     
171     int nTested = 0;
172     int nPass = 0;
173     
174     for (Class<?> testCls : testClasses){
175       nTested++;
176       
177       try {
178         try {
179           try { // check if there is a main(String[]) method
180             Method mainEntry = testCls.getDeclaredMethod("main", String[].class);
181             mainEntry.invoke(null, (Object) testArgs);
182
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});
186           }
187           
188           nPass++;
189           
190         } catch (NoSuchMethodException x) {
191           error("no suitable main() or runTests() in " + testCls.getName());
192         } catch (IllegalAccessException iax) {
193           error(iax.getMessage());
194         }
195         
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
202           System.exit(1);
203         } else {
204           error(ix.getCause().getMessage());
205         }
206       }
207     }    
208         
209     System.out.println();
210     System.out.printf("tested classes: %d, passed: %d\n", nTested, nPass);
211   }
212
213   static Class<?> loadTestClass (JPFClassLoader cl, Class<?> testJpfCls, String testClsName){
214     try {
215       Class<?> testCls = cl.loadClass(testClsName);
216       if (testJpfCls.isAssignableFrom(testCls)){
217         if (!Modifier.isAbstract(testCls.getModifiers())){
218           return testCls;
219         }
220       }
221       
222       return null;
223       
224     } catch (NoClassDefFoundError ncfx) {
225       error("class did not resolve: " + ncfx.getMessage());
226       return null;
227       
228     } catch (ClassNotFoundException cnfx) {
229       error("class not found " + cnfx.getMessage() + ", check test_classpath in jpf.properties");
230       return null;
231     }
232   }
233   
234   static boolean hasWildcard (String pattern){
235     return (pattern.indexOf('*') >= 0);
236   }
237   
238   static List<Class<?>> getTestClasses (JPFClassLoader cl, Class<?> testJpfCls, String[] testPathElements, String testClsPattern ){
239     List<Class<?>> testClasses = new ArrayList<Class<?>>();
240     
241     if (testClsPattern.startsWith(".")){
242       testClsPattern = "gov.nasa.jpf" + testClsPattern;
243     }
244     
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);  
249       }
250       testClasses.add(testCls);
251       
252     } else { // we have to recursively look into the testPathElements for potential test classes
253       List<String> classFileList = getClassFileList( testPathElements, testClsPattern);
254       
255       for (String candidate : classFileList){        
256         Class<?> testCls = loadTestClass( cl, testJpfCls, candidate);
257         if (testCls != null){
258           testClasses.add(testCls);
259         }
260       }
261     }
262     
263     return testClasses;
264   }
265   
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);
270         
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, '.');
276           list.add(clsName);
277         }
278       }
279     }
280   }
281   
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";
286     
287     for (String tpe : testPathElements){
288       File tp = new File(tpe);
289       int nPrefix = tp.getPath().length()+1;
290       collectMatchingFiles( nPrefix, tp, list, tcp);
291     }
292     
293     return list;
294   }
295   
296   static boolean isPublicStatic (Method m){
297     int mod = m.getModifiers();
298     return ((mod & (Modifier.PUBLIC | Modifier.STATIC)) == (Modifier.PUBLIC | Modifier.STATIC));
299   }
300   
301   static String[] getTestPathElements (Config conf){
302     String projectId = JPFSiteUtils.getCurrentProjectId();
303     
304     if (projectId != null) {
305       String testCpKey = projectId + ".test_classpath";
306       return  config.getCompactTrimmedStringArray(testCpKey);
307       
308     } else {
309       return new String[0];
310     }    
311   }
312   
313   static void addTestClassPath (JPFClassLoader cl, String[] testPathElements){
314     if (testPathElements != null) {
315       for (String pe : testPathElements) {
316         try {
317           cl.addURL(FileUtils.getURL(pe));
318         } catch (Throwable x) {
319           error("malformed test_classpath URL: " + pe);
320         }
321       }
322     }
323   }
324
325   static boolean isOptionArg(String a){
326     if (a != null && !a.isEmpty()){
327       char c = a.charAt(0);
328       if ((c == '+') || (c == '-')){
329         return true;
330       }
331     }
332     return false;
333   }
334   
335   static String getTestClassName(String[] args){
336     for (int i=0; i<args.length; i++){
337       String a = args[i];
338       if (a != null && !isOptionArg(a)){
339         return a;
340       }
341     }
342
343     return null;
344   }
345
346   // return everything after the first free arg
347   static String[] getTestArgs(String[] args){
348     int i;
349
350     for (i=0; i<args.length; i++){
351       String a = args[i];
352       if (a != null && !isOptionArg(a)){
353         break;
354       }
355     }
356
357     if (i >= args.length-1){
358       return new String[0];
359     } else {
360       String[] testArgs = new String[args.length-i-1];
361       System.arraycopy(args,i+1, testArgs, 0, testArgs.length);
362       return testArgs;
363     }
364   }
365
366
367 }