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 }