1 /*
   2  * Copyright (c) 2009, 2013 Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 import java.io.BufferedWriter;
  25 import java.io.File;
  26 import java.io.FileWriter;
  27 import java.io.IOException;
  28 import java.io.PrintStream;
  29 import java.io.PrintWriter;
  30 import java.lang.annotation.*;
  31 import java.lang.reflect.*;
  32 import java.util.ArrayList;
  33 import java.util.Collections;
  34 import java.util.HashMap;
  35 import java.util.List;
  36 import java.util.Map;
  37 
  38 import com.sun.tools.classfile.ClassFile;
  39 import com.sun.tools.classfile.TypeAnnotation;
  40 import com.sun.tools.classfile.TypeAnnotation.TargetType;
  41 
  42 public class Driver {
  43 
  44     private static final PrintStream out = System.out;
  45 
  46     public static void main(String[] args) throws Exception {
  47         if (args.length == 0 || args.length > 1)
  48             throw new IllegalArgumentException("Usage: java Driver <test-name>");
  49         String name = args[0];
  50         Class<?> clazz = Class.forName(name);
  51         new Driver().runDriver(clazz.newInstance());
  52     }
  53 
  54     protected void runDriver(Object object) throws Exception {
  55         int passed = 0, failed = 0;
  56         Class<?> clazz = object.getClass();
  57         out.println("Tests for " + clazz.getName());
  58 
  59         // Find methods
  60         for (Method method : clazz.getMethods()) {
  61             Map<String, TypeAnnotation.Position> expected = expectedOf(method);
  62             if (expected == null)
  63                 continue;
  64             if (method.getReturnType() != String.class)
  65                 throw new IllegalArgumentException("Test method needs to return a string: " + method);
  66             String testClass = testClassOf(method);
  67 
  68             try {
  69                 String compact = (String)method.invoke(object);
  70                 String fullFile = wrap(compact);
  71                 ClassFile cf = compileAndReturn(fullFile, testClass);
  72                 List<TypeAnnotation> actual = ReferenceInfoUtil.extendedAnnotationsOf(cf);
  73                 ReferenceInfoUtil.compare(expected, actual, cf);
  74                 out.println("PASSED:  " + method.getName());
  75                 ++passed;
  76             } catch (Throwable e) {
  77                 out.println("FAILED:  " + method.getName());
  78                 out.println("    " + e.toString());
  79                 ++failed;
  80             }
  81         }
  82 
  83         out.println();
  84         int total = passed + failed;
  85         out.println(total + " total tests: " + passed + " PASSED, " + failed + " FAILED");
  86 
  87         out.flush();
  88 
  89         if (failed != 0)
  90             throw new RuntimeException(failed + " tests failed");
  91     }
  92 
  93     private Map<String, TypeAnnotation.Position> expectedOf(Method m) {
  94         TADescription ta = m.getAnnotation(TADescription.class);
  95         TADescriptions tas = m.getAnnotation(TADescriptions.class);
  96 
  97         if (ta == null && tas == null)
  98             return null;
  99 
 100         Map<String, TypeAnnotation.Position> result =
 101             new HashMap<String, TypeAnnotation.Position>();
 102 
 103         if (ta != null)
 104             result.putAll(expectedOf(ta));
 105 
 106         if (tas != null) {
 107             for (TADescription a : tas.value()) {
 108                 result.putAll(expectedOf(a));
 109             }
 110         }
 111 
 112         return result;
 113     }
 114 
 115     private Map<String, TypeAnnotation.Position> expectedOf(TADescription d) {
 116         String annoName = d.annotation();
 117 
 118         TypeAnnotation.Position p = new TypeAnnotation.Position();
 119         p.type = d.type();
 120         if (d.offset() != NOT_SET)
 121             p.offset = d.offset();
 122         if (d.lvarOffset().length != 0)
 123             p.lvarOffset = d.lvarOffset();
 124         if (d.lvarLength().length != 0)
 125             p.lvarLength = d.lvarLength();
 126         if (d.lvarIndex().length != 0)
 127             p.lvarIndex = d.lvarIndex();
 128         if (d.boundIndex() != NOT_SET)
 129             p.bound_index = d.boundIndex();
 130         if (d.paramIndex() != NOT_SET)
 131             p.parameter_index = d.paramIndex();
 132         if (d.typeIndex() != NOT_SET)
 133             p.type_index = d.typeIndex();
 134         if (d.exceptionIndex() != NOT_SET)
 135             p.exception_index = d.exceptionIndex();
 136         if (d.genericLocation().length != 0) {
 137             p.location = TypeAnnotation.Position.getTypePathFromBinary(wrapIntArray(d.genericLocation()));
 138         }
 139 
 140         return Collections.singletonMap(annoName, p);
 141     }
 142 
 143     private List<Integer> wrapIntArray(int[] ints) {
 144         List<Integer> list = new ArrayList<Integer>(ints.length);
 145         for (int i : ints)
 146             list.add(i);
 147         return list;
 148     }
 149 
 150     private String testClassOf(Method m) {
 151         TestClass tc = m.getAnnotation(TestClass.class);
 152         if (tc != null) {
 153             return tc.value();
 154         } else {
 155             return "Test";
 156         }
 157     }
 158 
 159     private ClassFile compileAndReturn(String fullFile, String testClass) throws Exception {
 160         File source = writeTestFile(fullFile);
 161         File clazzFile = compileTestFile(source, testClass);
 162         return ClassFile.read(clazzFile);
 163     }
 164 
 165     protected File writeTestFile(String fullFile) throws IOException {
 166         File f = new File("Test.java");
 167         PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(f)));
 168         out.println(fullFile);
 169         out.close();
 170         return f;
 171     }
 172 
 173     protected File compileTestFile(File f, String testClass) {
 174         int rc = com.sun.tools.javac.Main.compile(new String[] { "-source", "1.8", "-g", f.getPath() });
 175         if (rc != 0)
 176             throw new Error("compilation failed. rc=" + rc);
 177         String path;
 178         if (f.getParent() != null) {
 179             path = f.getParent();
 180         } else {
 181             path = "";
 182         }
 183 
 184         return new File(path + testClass + ".class");
 185     }
 186 
 187     private String wrap(String compact) {
 188         StringBuilder sb = new StringBuilder();
 189 
 190         // Automatically import java.util
 191         sb.append("\nimport java.util.*;");
 192         sb.append("\nimport java.lang.annotation.*;");
 193 
 194         sb.append("\n\n");
 195         boolean isSnippet = !(compact.startsWith("class")
 196                               || compact.contains(" class"))
 197                             && !compact.contains("interface")
 198                             && !compact.contains("enum");
 199         if (isSnippet)
 200             sb.append("class Test {\n");
 201 
 202         sb.append(compact);
 203         sb.append("\n");
 204 
 205         if (isSnippet)
 206             sb.append("}\n\n");
 207 
 208         if (isSnippet) {
 209             // Have a few common nested types for testing
 210             sb.append("class Outer { class Inner {} }");
 211             sb.append("class SOuter { static class SInner {} }");
 212             sb.append("class GOuter<X, Y> { class GInner<X, Y> {} }");
 213         }
 214 
 215         // create A ... F annotation declarations
 216         sb.append("\n@interface A {}");
 217         sb.append("\n@interface B {}");
 218         sb.append("\n@interface C {}");
 219         sb.append("\n@interface D {}");
 220         sb.append("\n@interface E {}");
 221         sb.append("\n@interface F {}");
 222 
 223         // create TA ... TF proper type annotations
 224         sb.append("\n");
 225         sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TA {}");
 226         sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TB {}");
 227         sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TC {}");
 228         sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TD {}");
 229         sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TE {}");
 230         sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TF {}");
 231         sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TG {}");
 232         sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TH {}");
 233         sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TI {}");
 234         sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TJ {}");
 235         sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TK {}");
 236         sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TL {}");
 237         sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface TM {}");
 238 
 239         // create RTA, RTAs, RTB, RTBs for repeating type annotations
 240         sb.append("\n");
 241         sb.append("\n@Repeatable(RTAs.class) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface RTA {}");
 242         sb.append("\n@Repeatable(RTBs.class) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface RTB {}");
 243 
 244         sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface RTAs { RTA[] value(); }");
 245         sb.append("\n@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @interface RTBs { RTB[] value(); }");
 246 
 247         sb.append("\n@Target(value={ElementType.TYPE,ElementType.FIELD,ElementType.METHOD,ElementType.PARAMETER,ElementType.CONSTRUCTOR,ElementType.LOCAL_VARIABLE})");
 248         sb.append("\n@interface Decl {}");
 249 
 250         return sb.toString();
 251     }
 252 
 253     public static final int NOT_SET = -888;
 254 
 255 }
 256 
 257 @Retention(RetentionPolicy.RUNTIME)
 258 @Target(ElementType.METHOD)
 259 @interface TADescription {
 260     String annotation();
 261 
 262     TargetType type();
 263     int offset() default Driver.NOT_SET;
 264     int[] lvarOffset() default { };
 265     int[] lvarLength() default { };
 266     int[] lvarIndex() default { };
 267     int boundIndex() default Driver.NOT_SET;
 268     int paramIndex() default Driver.NOT_SET;
 269     int typeIndex() default Driver.NOT_SET;
 270     int exceptionIndex() default Driver.NOT_SET;
 271 
 272     int[] genericLocation() default {};
 273 }
 274 
 275 @Retention(RetentionPolicy.RUNTIME)
 276 @Target(ElementType.METHOD)
 277 @interface TADescriptions {
 278     TADescription[] value() default {};
 279 }
 280 
 281 /**
 282  * The name of the class that should be analyzed.
 283  * Should only need to be provided when analyzing inner classes.
 284  */
 285 @Retention(RetentionPolicy.RUNTIME)
 286 @Target(ElementType.METHOD)
 287 @interface TestClass {
 288     String value() default "Test";
 289 }