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.IOException; 32 import java.util.ArrayList; 33 import java.util.HashMap; 34 35 import org.objectweb.asm.AnnotationVisitor; 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 import java.io.FileInputStream; 43 44 /** 45 * Finder which reads class files to locate those with an appropriate annotations 46 * to qualify it as a "test". If an appropriate JUnit test, it is then scanned 47 * for methods that are test methods and this information is added to the 48 * TestDescription which is produced. The functionality here is currently based 49 * on what you would find in a JUnit 4.x test suite which uses annotations for 50 * marking tests. 51 * 52 * The scanner can operate in two ways- either by looking for .class files and 53 * scanning them, or by looking for .java files and then loading the corresponding 54 * .class file. 55 * 56 * @see com.sun.javatest.TestFinder 57 * @see com.sun.javatest.TestDescription 58 */ 59 public class JUnitAnnotationTestFinder extends JUnitTestFinder { 60 /** 61 * Constructs the list of file names to exclude for pruning in the search 62 * for files to examine for test descriptions. 63 */ 64 public JUnitAnnotationTestFinder() { 65 exclude(excludeNames); 66 } 67 68 /** 69 * Decode the arg at a specified position in the arg array. If overridden 70 * by a subtype, the subtype should try and decode any arg it recognizes, 71 * and then call super.decodeArg to give the superclass(es) a chance to 72 * recognize any arguments. 73 * 74 * @param args The array of arguments 75 * @param i The next argument to be decoded. 76 * @return The number of elements consumed in the array; for example, 77 * for a simple option like "-v" the result should be 1; for an 78 * option with an argument like "-f file" the result should be 79 * 2, etc. 80 * @throws TestFinder.Fault If there is a problem with the value of the current arg, 81 * such as a bad value to an option, the Fault exception can be 82 * thrown. The exception should NOT be thrown if the current 83 * arg is unrecognized: in that case, an implementation should 84 * delegate the call to the supertype. 85 */ 86 protected void decodeAllArgs(String[] args) throws Fault { 87 super.decodeAllArgs(args); 88 } 89 90 /** 91 * Scan a file, looking for test descriptions and/or more files to scan. 92 * @param file The file to scan 93 */ 94 public void scan(File file) { 95 currFile = file; 96 if (file.isDirectory()) 97 scanDirectory(file); 98 else 99 scanFile(file); 100 } 101 102 103 /** 104 * Call to register the methods which are test methods. 105 */ 106 public void foundTestMethod(String name) { 107 testMethods.add(name); 108 } 109 110 //-----internal routines---------------------------------------------------- 111 112 /** 113 * Scan a directory, looking for more files to scan 114 * @param dir The directory to scan 115 */ 116 private void scanDirectory(File dir) { 117 118 // scan the contents of the directory, checking for 119 // subdirectories and other files that should be scanned 120 String[] names = dir.list(); 121 for (int i = 0; i < names.length; i++) { 122 String name = names[i]; 123 // if the file should be ignored, skip it 124 // This is typically for directories like SCCS etc 125 if (excludeList.containsKey(name)) 126 continue; 127 128 File file = new File(dir, name); 129 if (file.isDirectory()) { 130 //System.out.println("dir: " + dir.getAbsolutePath()); 131 // if its a directory, add it to the list to be scanned 132 //foundFile(file); 133 scanDirectory(file); 134 } else { 135 // if its a file, check its extension 136 int dot = name.indexOf('.'); 137 if (dot == -1) 138 continue; 139 String extn = name.substring(dot); 140 if (extensionTable.containsKey(extn)) { 141 // extension has a comment reader, so add it to the 142 // list to be scanned 143 foundFile(file); 144 } 145 } 146 } 147 } 148 149 /** 150 * Scan a file, looking for comments and in the comments, for test 151 * description data. 152 * @param file The file to scan 153 */ 154 protected void scanFile(File file) { 155 testMethods = new ArrayList<>(); // new every time we visit a new class 156 tdValues = new HashMap<>(); 157 158 String name = file.getName(); 159 int dot = name.indexOf('.'); 160 if (dot == -1) 161 return; 162 163 String classFile=""; 164 if (scanClasses) { 165 classFile = file.getPath(); 166 } else { 167 String currentDir=new File("").getAbsolutePath(); 168 String sources = name; 169 String filePath=file.getAbsolutePath(). 170 substring(currentDir.length()+1, file.getAbsolutePath().length()); 171 172 if (filePath.startsWith("tests")){ 173 classFile=currentDir+File.separator+"classes"+File.separator+filePath.substring(6,filePath.length()); 174 } else if (filePath.startsWith("test")){ 175 classFile=currentDir+File.separator+"classes"+File.separator+filePath.substring(5,filePath.length()); 176 } else { 177 return; 178 } 179 180 classFile = file.getAbsolutePath().replaceFirst("tests", "classes"); 181 } 182 183 dot = classFile.lastIndexOf('.'); 184 classFile = classFile.substring(0, dot)+ ".class"; 185 186 try { 187 if(!new File(classFile).exists()){ 188 System.out.println("classFile does not exist: " + classFile); 189 return; 190 } 191 try { 192 FileInputStream fis = new FileInputStream(classFile); 193 ClassReader cr = new ClassReader(fis); 194 cr.accept(new JUnitAnnotationClassVisitor(this), 0); 195 // action happens in visit(...) below 196 197 // methods are necessary for this to be a test 198 // could expand this to allow other junit annotations 199 if (testMethods.size() != 0) { 200 StringBuilder tms = new StringBuilder(); 201 for (String n: testMethods) { 202 tms.append(n); 203 tms.append(" "); 204 } 205 tms.deleteCharAt(tms.length()-1); 206 tdValues.put("source", file.getPath()); 207 tdValues.put("junit.testmethods", tms.toString()); 208 tdValues.put("junit.finderscantype", "annotation"); 209 tdValues.put("keywords", "junit junit4"); 210 tdValues.put("executeClass", cr.getClassName().replaceAll("/", ".")); 211 212 // consider stripping the .java or .class off currFile 213 foundTestDescription(tdValues, currFile, 0); 214 } 215 216 } catch(IOException e) { 217 error(i18n, "finder.classioe", classFile); 218 } // catch 219 } catch (Exception e) { 220 System.out.println("!!! Exception: " + e); 221 } 222 223 return; 224 } 225 226 class JUnitAnnotationMethodVisitor extends MethodVisitor { 227 private JUnitAnnotationTestFinder outer; 228 229 public JUnitAnnotationMethodVisitor(JUnitAnnotationTestFinder outer) { 230 super(Opcodes.ASM4); 231 this.outer = outer; 232 } 233 234 public AnnotationVisitor visitAnnotation(String string, boolean b) { 235 if (outer.methodAnnotation.equals(string)) 236 outer.foundTestMethod(outer.currMethod); 237 238 return null; 239 } 240 } 241 242 class JUnitAnnotationClassVisitor extends ClassVisitor { 243 private JUnitAnnotationTestFinder outer; 244 private JUnitAnnotationMethodVisitor methodVisitor; 245 246 public JUnitAnnotationClassVisitor(JUnitAnnotationTestFinder outer) { 247 super(Opcodes.ASM4); 248 this.outer = outer; 249 methodVisitor = new JUnitAnnotationMethodVisitor(outer); 250 } 251 252 public void visit(int version, int access, String name, String signature, 253 String superName, String[] interfaces) { 254 if (verbose) 255 System.out.println("found class " + name + " with super " + superName); 256 } 257 258 public MethodVisitor visitMethod(int access, String name, String desc, 259 String signature, String[] exceptions) { 260 if (access == Opcodes.ACC_PUBLIC) { 261 outer.currMethod = name; 262 return methodVisitor; 263 } else 264 return null; 265 } 266 } 267 268 //----------member variables------------------------------------------------ 269 270 protected String currMethod; 271 private static I18NResourceBundle i18n = I18NResourceBundle.getBundleForClass(JUnitAnnotationTestFinder.class); 272 protected String methodAnnotation = "Lorg/junit/Test;"; 273 } 274