1 /* 2 * $Id$ 3 * 4 * Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved. 5 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 6 * 7 * This code is free software; you can redistribute it and/or modify it 8 * under the terms of the GNU General Public License version 2 only, as 9 * published by the Free Software Foundation. Oracle designates this 10 * particular file as subject to the "Classpath" exception as provided 11 * by Oracle in the LICENSE file that accompanied this code. 12 * 13 * This code is distributed in the hope that it will be useful, but WITHOUT 14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 16 * version 2 for more details (a copy is included in the LICENSE file that 17 * accompanied this code). 18 * 19 * You should have received a copy of the GNU General Public License version 20 * 2 along with this work; if not, write to the Free Software Foundation, 21 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 22 * 23 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 24 * or visit www.oracle.com if you need additional information or have any 25 * questions. 26 */ 27 28 package com.sun.javatest.junit; 29 30 import java.io.File; 31 import java.io.FileInputStream; 32 import java.io.IOException; 33 import java.util.ArrayList; 34 import java.util.HashMap; 35 36 import org.objectweb.asm.ClassReader; 37 import org.objectweb.asm.ClassVisitor; 38 import org.objectweb.asm.MethodVisitor; 39 import org.objectweb.asm.Opcodes; 40 41 import com.sun.javatest.util.I18NResourceBundle; 42 43 /** 44 * Finder which reads class files to locate those with an appropriate base class 45 * to qualify it as a "test". If an appropriate JUnit test, it is then scanned 46 * for methods that are test methods and this information is added to the 47 * TestDescription which is produced. 48 * 49 * Note that this class is not reentrant and must be protected by the calling 50 * parties. 51 * 52 * @see com.sun.javatest.TestFinder 53 * @see com.sun.javatest.TestDescription 54 */ 55 public class JUnitSuperTestFinder extends JUnitTestFinder { 56 /** 57 * Constructs the list of file names to exclude for pruning in the search 58 * for files to examine for test descriptions. 59 */ 60 public JUnitSuperTestFinder() { 61 exclude(excludeNames); 62 } 63 64 /** 65 * Decode the arg at a specified position in the arg array. If overridden 66 * by a subtype, the subtype should try and decode any arg it recognizes, 67 * and then call super.decodeArg to give the superclass(es) a chance to 68 * recognize any arguments. 69 * 70 * @param args The array of arguments 71 * @param i The next argument to be decoded. 72 * @return The number of elements consumed in the array; for example, 73 * for a simple option like "-v" the result should be 1; for an 74 * option with an argument like "-f file" the result should be 75 * 2, etc. 76 * @throws TestFinder.Fault If there is a problem with the value of the current arg, 77 * such as a bad value to an option, the Fault exception can be 78 * thrown. The exception should NOT be thrown if the current 79 * arg is unrecognized: in that case, an implementation should 80 * delegate the call to the supertype. 81 */ 82 protected void decodeAllArgs(String[] args) throws Fault { 83 super.decodeAllArgs(args); 84 85 for (int i = 0; i < args.length; i++) { 86 if ("-superclass".equalsIgnoreCase(args[i])) { 87 if (args.length <= i+1) { 88 error(i18n, "finder.missingsuper"); 89 return; 90 } 91 requiredSuperclass.add(args[++i]); 92 } 93 } // for 94 95 // the default if the user does not give one 96 if (requiredSuperclass.size() == 0) 97 requiredSuperclass.add("junit.framework.TestCase"); 98 } 99 100 /** 101 * Scan a file, looking for test descriptions and/or more files to scan. 102 * @param file The file to scan 103 */ 104 public void scan(File file) { 105 currFile = file; 106 if (file.isDirectory()) 107 scanDirectory(file); 108 else 109 scanFile(file); 110 } 111 112 //-----internal routines---------------------------------------------------- 113 114 /** 115 * Scan a directory, looking for more files to scan 116 * @param dir The directory to scan 117 */ 118 private void scanDirectory(File dir) { 119 120 // scan the contents of the directory, checking for 121 // subdirectories and other files that should be scanned 122 String[] names = dir.list(); 123 for (int i = 0; i < names.length; i++) { 124 String name = names[i]; 125 // if the file should be ignored, skip it 126 // This is typically for directories like SCCS etc 127 if (excludeList.containsKey(name)) 128 continue; 129 130 File file = new File(dir, name); 131 if (file.isDirectory()) { 132 if (verbose) 133 System.out.println("dir: " + dir.getAbsolutePath()); 134 // if its a directory, add it to the list to be scanned 135 scanDirectory(file); 136 } else { 137 // if its a file, check its extension 138 int dot = name.indexOf('.'); 139 if (dot == -1) 140 continue; 141 String extn = name.substring(dot); 142 if (extensionTable.containsKey(extn)) { 143 // extension has a comment reader, so add it to the 144 // list to be scanned 145 foundFile(file); 146 } 147 } 148 } 149 } 150 151 /** 152 * Scan a file, looking for comments and in the comments, for test 153 * description data. 154 * @param file The file to scan 155 */ 156 protected void scanFile(File file) { 157 testMethods = new ArrayList<>(); // new every time we visit a new class 158 tdValues = new HashMap<>(); 159 160 String name = file.getName(); 161 if (verbose) 162 System.out.println(i18n.getString("finder.whichfile", name)); 163 164 int dot = name.indexOf('.'); 165 if (dot == -1) 166 return; 167 168 String classFile=""; 169 if (scanClasses) { 170 classFile = file.getPath(); 171 } else { 172 String currentDir=new File("").getAbsolutePath(); 173 String sources = name; 174 String filePath=file.getAbsolutePath(). 175 substring(currentDir.length()+1, file.getAbsolutePath().length()); 176 177 if (filePath.startsWith("tests")){ 178 classFile=currentDir+File.separator+"classes"+File.separator+filePath.substring(6,filePath.length()); 179 } else if (filePath.startsWith("test")){ 180 classFile=currentDir+File.separator+"classes"+File.separator+filePath.substring(5,filePath.length()); 181 } else { 182 return; 183 } 184 185 classFile = file.getAbsolutePath().replaceFirst("tests", "classes"); 186 187 } 188 189 dot = classFile.lastIndexOf('.'); 190 classFile = classFile.substring(0, dot)+ ".class"; 191 192 try { 193 if(!new File(classFile).exists()){ 194 System.out.println("classFile does not exist: " + classFile); 195 return; 196 } 197 try { 198 FileInputStream fis = new FileInputStream(classFile); 199 ClassReader cr = new ClassReader(fis); 200 cr.accept(new JUnitClassVisitor(this), 0); 201 // action happens in visit(...) below 202 203 if (tdValues.get("executeClass") == null) 204 return; // not interested in this class 205 206 if (testMethods.size() != 0) { 207 StringBuilder tms = new StringBuilder(); 208 for (String n: testMethods) { 209 tms.append(n); 210 tms.append(" "); 211 } 212 tms.deleteCharAt(tms.length()-1); 213 214 tdValues.put("junit.testmethods", tms.toString()); 215 } 216 217 tdValues.put("keywords", "junit junit3"); 218 tdValues.put("junit.finderscantype", "superclass"); 219 tdValues.put("source", file.getPath()); 220 StringBuilder cls = new StringBuilder(); 221 for (String n: requiredSuperclass) { 222 cls.append(n); 223 cls.append(" "); 224 } 225 cls.deleteCharAt(cls.length()-1); 226 tdValues.put("junit.findersuperclasses", cls.toString()); 227 228 // consider stripping the .java or .class off currFile 229 foundTestDescription(tdValues, currFile, 0); 230 } catch(IOException e) { 231 error(i18n, "finder.classioe", classFile); 232 } // catch 233 } catch (Exception e) { 234 System.out.println("!!! Exception: " + e); 235 } 236 return; 237 238 } 239 240 241 /** 242 * Lookup current name among requested superclasses. 243 */ 244 private boolean isMatchSuper(String cname) { 245 if (cname == null || cname.equals("java.lang.Object")) 246 return false; 247 248 for(String n: requiredSuperclass) { 249 if (cname.equals(n)) 250 return true; 251 } // for 252 253 try { 254 return isMatchSuper(mfv.getSuperClass(cname)); 255 } catch (IOException e) { 256 error(i18n, "finder.cantsuper", cname); 257 return false; 258 } 259 } 260 261 /** 262 * Override this if you want to look for method names differently; the 263 * default implementation looks for methods that begin with the string 264 * "test". If you wish to do full signature analysis, override 265 * visitMethod(...) which is part of the ASM interface. 266 * 267 */ 268 public boolean isTestMethodSignature(String sig) { 269 if (sig.startsWith(initialTag)) 270 return true; 271 else 272 return false; 273 } 274 275 class JUnitClassVisitor extends ClassVisitor { 276 private JUnitSuperTestFinder outer; 277 278 JUnitClassVisitor(JUnitSuperTestFinder outer) { 279 super(Opcodes.ASM4); 280 this.outer = outer; 281 } 282 283 public void visit(int version, int access, String name, String signature, 284 String superName, String[] interfaces) { 285 if (verbose) 286 System.out.println("found class " + name + " with super " + superName); 287 288 if (outer.isMatchSuper(superName.replaceAll("/", "."))) { 289 outer.tdValues.put("executeClass", name.replaceAll("/", ".")); 290 } 291 } 292 293 /** 294 * Looks for methods which are test methods by calling <tt>isTestMethodSignature</tt>. 295 * You can override that method or this one. If overriding this one, 296 * use foundTestMethod(String) to register any test methods which you 297 * find. 298 */ 299 public MethodVisitor visitMethod(int access, String name, String desc, 300 String signature, String[] exceptions) { 301 if (access == Opcodes.ACC_PUBLIC) { 302 if (outer.isTestMethodSignature(name)) 303 outer.foundTestMethod(name); 304 } 305 306 return null; 307 } 308 } 309 310 private static class MethodFinderVisitor extends ClassVisitor { 311 312 public MethodFinderVisitor() { 313 super(Opcodes.ASM4); 314 } 315 /** 316 * Return the given class' superclass name in dotted notation. 317 */ 318 public String getSuperClass(String cname) throws IOException { 319 ClassReader cr = new ClassReader(cname); 320 cr.accept(this, 0); 321 return thisSupername.replaceAll("/", "."); 322 } 323 324 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { 325 thisSupername = superName; 326 } 327 328 String thisSupername; 329 } 330 331 332 333 //----------member variables------------------------------------------------ 334 335 protected ArrayList<String> requiredSuperclass = new ArrayList<>(); 336 protected String initialTag = "test"; 337 protected final MethodFinderVisitor mfv = new MethodFinderVisitor(); 338 protected static final I18NResourceBundle i18n = I18NResourceBundle.getBundleForClass(JUnitSuperTestFinder.class); 339 }