/* * $Id$ * * Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.javatest.junit; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import com.sun.javatest.util.I18NResourceBundle; /** * Finder which reads class files to locate those with an appropriate base class * to qualify it as a "test". If an appropriate JUnit test, it is then scanned * for methods that are test methods and this information is added to the * TestDescription which is produced. * * Note that this class is not reentrant and must be protected by the calling * parties. * * @see com.sun.javatest.TestFinder * @see com.sun.javatest.TestDescription */ public class JUnitSuperTestFinder extends JUnitTestFinder { /** * Constructs the list of file names to exclude for pruning in the search * for files to examine for test descriptions. */ public JUnitSuperTestFinder() { exclude(excludeNames); } /** * Decode the arg at a specified position in the arg array. If overridden * by a subtype, the subtype should try and decode any arg it recognizes, * and then call super.decodeArg to give the superclass(es) a chance to * recognize any arguments. * * @param args The array of arguments * @param i The next argument to be decoded. * @return The number of elements consumed in the array; for example, * for a simple option like "-v" the result should be 1; for an * option with an argument like "-f file" the result should be * 2, etc. * @throws TestFinder.Fault If there is a problem with the value of the current arg, * such as a bad value to an option, the Fault exception can be * thrown. The exception should NOT be thrown if the current * arg is unrecognized: in that case, an implementation should * delegate the call to the supertype. */ protected void decodeAllArgs(String[] args) throws Fault { super.decodeAllArgs(args); for (int i = 0; i < args.length; i++) { if ("-superclass".equalsIgnoreCase(args[i])) { if (args.length <= i+1) { error(i18n, "finder.missingsuper"); return; } requiredSuperclass.add(args[++i]); } } // for // the default if the user does not give one if (requiredSuperclass.size() == 0) requiredSuperclass.add("junit.framework.TestCase"); } /** * Scan a file, looking for test descriptions and/or more files to scan. * @param file The file to scan */ public void scan(File file) { currFile = file; if (file.isDirectory()) scanDirectory(file); else scanFile(file); } //-----internal routines---------------------------------------------------- /** * Scan a directory, looking for more files to scan * @param dir The directory to scan */ private void scanDirectory(File dir) { // scan the contents of the directory, checking for // subdirectories and other files that should be scanned String[] names = dir.list(); for (int i = 0; i < names.length; i++) { String name = names[i]; // if the file should be ignored, skip it // This is typically for directories like SCCS etc if (excludeList.containsKey(name)) continue; File file = new File(dir, name); if (file.isDirectory()) { if (verbose) System.out.println("dir: " + dir.getAbsolutePath()); // if its a directory, add it to the list to be scanned scanDirectory(file); } else { // if its a file, check its extension int dot = name.indexOf('.'); if (dot == -1) continue; String extn = name.substring(dot); if (extensionTable.containsKey(extn)) { // extension has a comment reader, so add it to the // list to be scanned foundFile(file); } } } } /** * Scan a file, looking for comments and in the comments, for test * description data. * @param file The file to scan */ protected void scanFile(File file) { testMethods = new ArrayList<>(); // new every time we visit a new class tdValues = new HashMap<>(); String name = file.getName(); if (verbose) System.out.println(i18n.getString("finder.whichfile", name)); int dot = name.indexOf('.'); if (dot == -1) return; String classFile=""; if (scanClasses) { classFile = file.getPath(); } else { String currentDir=new File("").getAbsolutePath(); String sources = name; String filePath=file.getAbsolutePath(). substring(currentDir.length()+1, file.getAbsolutePath().length()); if (filePath.startsWith("tests")){ classFile=currentDir+File.separator+"classes"+File.separator+filePath.substring(6,filePath.length()); } else if (filePath.startsWith("test")){ classFile=currentDir+File.separator+"classes"+File.separator+filePath.substring(5,filePath.length()); } else { return; } classFile = file.getAbsolutePath().replaceFirst("tests", "classes"); } dot = classFile.lastIndexOf('.'); classFile = classFile.substring(0, dot)+ ".class"; try { if(!new File(classFile).exists()){ System.out.println("classFile does not exist: " + classFile); return; } try { FileInputStream fis = new FileInputStream(classFile); ClassReader cr = new ClassReader(fis); cr.accept(new JUnitClassVisitor(this), 0); // action happens in visit(...) below if (tdValues.get("executeClass") == null) return; // not interested in this class if (testMethods.size() != 0) { StringBuilder tms = new StringBuilder(); for (String n: testMethods) { tms.append(n); tms.append(" "); } tms.deleteCharAt(tms.length()-1); tdValues.put("junit.testmethods", tms.toString()); } tdValues.put("keywords", "junit junit3"); tdValues.put("junit.finderscantype", "superclass"); tdValues.put("source", file.getPath()); StringBuilder cls = new StringBuilder(); for (String n: requiredSuperclass) { cls.append(n); cls.append(" "); } cls.deleteCharAt(cls.length()-1); tdValues.put("junit.findersuperclasses", cls.toString()); // consider stripping the .java or .class off currFile foundTestDescription(tdValues, currFile, 0); } catch(IOException e) { error(i18n, "finder.classioe", classFile); } // catch } catch (Exception e) { System.out.println("!!! Exception: " + e); } return; } /** * Lookup current name among requested superclasses. */ private boolean isMatchSuper(String cname) { if (cname == null || cname.equals("java.lang.Object")) return false; for(String n: requiredSuperclass) { if (cname.equals(n)) return true; } // for try { return isMatchSuper(mfv.getSuperClass(cname)); } catch (IOException e) { error(i18n, "finder.cantsuper", cname); return false; } } /** * Override this if you want to look for method names differently; the * default implementation looks for methods that begin with the string * "test". If you wish to do full signature analysis, override * visitMethod(...) which is part of the ASM interface. * */ public boolean isTestMethodSignature(String sig) { if (sig.startsWith(initialTag)) return true; else return false; } class JUnitClassVisitor extends ClassVisitor { private JUnitSuperTestFinder outer; JUnitClassVisitor(JUnitSuperTestFinder outer) { super(Opcodes.ASM4); this.outer = outer; } public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { if (verbose) System.out.println("found class " + name + " with super " + superName); if (outer.isMatchSuper(superName.replaceAll("/", "."))) { outer.tdValues.put("executeClass", name.replaceAll("/", ".")); } } /** * Looks for methods which are test methods by calling isTestMethodSignature. * You can override that method or this one. If overriding this one, * use foundTestMethod(String) to register any test methods which you * find. */ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { if (access == Opcodes.ACC_PUBLIC) { if (outer.isTestMethodSignature(name)) outer.foundTestMethod(name); } return null; } } private static class MethodFinderVisitor extends ClassVisitor { public MethodFinderVisitor() { super(Opcodes.ASM4); } /** * Return the given class' superclass name in dotted notation. */ public String getSuperClass(String cname) throws IOException { ClassReader cr = new ClassReader(cname); cr.accept(this, 0); return thisSupername.replaceAll("/", "."); } public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { thisSupername = superName; } String thisSupername; } //----------member variables------------------------------------------------ protected ArrayList requiredSuperclass = new ArrayList<>(); protected String initialTag = "test"; protected final MethodFinderVisitor mfv = new MethodFinderVisitor(); protected static final I18NResourceBundle i18n = I18NResourceBundle.getBundleForClass(JUnitSuperTestFinder.class); }