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