test/tools/javap/output/Tester.java

Fri, 03 May 2013 09:56:56 -0700

author
jjg
date
Fri, 03 May 2013 09:56:56 -0700
changeset 1721
abd153854f16
parent 1643
1f8c28134ffc
child 2525
2eb010b6cb22
permissions
-rw-r--r--

8012728: Normalize @ignore comments on langtools tests
Reviewed-by: vromero, mcimadamore

     1 /*
     2  * Copyright (c) 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  */
    24 import java.io.*;
    25 import java.util.*;
    26 import java.lang.annotation.*;
    27 import java.lang.reflect.InvocationTargetException;
    29 /**
    30  * {@code Tester} is an abstract test-driver that provides the logic
    31  * to execute test-cases, grouped by test classes.
    32  * A test class is a main class extending this class, that instantiate
    33  * itself, and calls the {@link run} method, passing any command line
    34  * arguments.
    35  * <p>
    36  * The {@code run} method, expects arguments to identify test-case classes.
    37  * A test-case class is a class extending the test class, and annotated
    38  * with {@code TestCase}.
    39  * <p>
    40  * If no test-cases are specified, the test class directory is searched for
    41  * co-located test-case classes (i.e. any class extending the test class,
    42  * annotated with  {@code TestCase}).
    43  * <p>
    44  * Besides serving to group test-cases, extending the driver allow
    45  * setting up a test-case template, and possibly overwrite default
    46  * test-driver behaviour.
    47  */
    48 public abstract class Tester {
    50     private static boolean debug = false;
    51     private static final PrintStream out = System.err;
    52     private static final PrintStream err = System.err;
    55     protected void run(String... args) throws Exception {
    57         final File classesdir = new File(System.getProperty("test.classes", "."));
    59         String[] classNames = args;
    61         // If no test-cases are specified, we regard all co-located classes
    62         // as potential test-cases.
    63         if (args.length == 0) {
    64             final String pattern =  ".*\\.class";
    65             final File classFiles[] = classesdir.listFiles(new FileFilter() {
    66                     public boolean accept(File f) {
    67                         return f.getName().matches(pattern);
    68                     }
    69                 });
    70             ArrayList<String> names = new ArrayList<String>(classFiles.length);
    71             for (File f : classFiles) {
    72                 String fname = f.getName();
    73                 names.add(fname.substring(0, fname.length() -6));
    74             }
    75             classNames = names.toArray(new String[names.size()]);
    76         } else {
    77             debug = true;
    78         }
    79         // Test-cases must extend the driver type, and be marked
    80         // @TestCase. Other arguments (classes) are ignored.
    81         // Test-cases are instantiated, and thereby executed.
    82         for (String clname : classNames) {
    83             try {
    84                 final Class tclass = Class.forName(clname);
    85                 if  (!getClass().isAssignableFrom(tclass)) continue;
    86                 TestCase anno = (TestCase) tclass.getAnnotation(TestCase.class);
    87                 if (anno == null) continue;
    88                 if (!debug) {
    89                     ignore i = (ignore) tclass.getAnnotation(ignore.class);
    90                     if (i != null) {
    91                         out.println("Ignore: " + clname);
    92                         ignored++;
    93                         continue;
    94                     }
    95                 }
    96                 out.println("TestCase: " + clname);
    97                 cases++;
    98                 Tester tc = (Tester) tclass.getConstructor().newInstance();
    99                 if (tc.errors > 0) {
   100                     error("" + tc.errors + " test points failed in " + clname);
   101                     errors += tc.errors - 1;
   102                     fcases++;
   103                 }
   104             } catch(ReflectiveOperationException roe) {
   105                 error("Warning: " + clname + " - ReflectiveOperationException");
   106                 roe.printStackTrace(err);
   107             } catch(Exception unknown) {
   108                 error("Warning: " + clname + " - uncaught exception");
   109                 unknown.printStackTrace(err);
   110             }
   111         }
   113         String imsg = ignored > 0 ? " (" +  ignored + " ignored)" : "";
   114         if (errors > 0)
   115             throw new Error(errors + " error, in " + fcases + " of " + cases + " test-cases" + imsg);
   116         else
   117             err.println("" + cases + " test-cases executed" + imsg + ", no errors");
   118     }
   121     /**
   122      * Test-cases must be marked with the {@code TestCase} annotation,
   123      * as well as extend {@code Tester} (or an driver extension
   124      * specified as the first argument to the {@code main()} method.
   125      */
   126     @Retention(RetentionPolicy.RUNTIME)
   127     @interface TestCase { }
   129     /**
   130      * Individual test-cases failing due to product bugs, may temporarily
   131      * be excluded by marking them like this, (where "at-" is replaced by "@")
   132      * at-ignore // 1234567: bug synopsis
   133      */
   134     @Retention(RetentionPolicy.RUNTIME)
   135     @interface ignore { }
   137     /**
   138      * Test-cases are classes extending {@code Tester}, and
   139      * calling {@link setSrc}, followed by one or more invocations
   140      * of {@link verify} in the body of the constructor.
   141      * <p>
   142      * Sets a default test-case template, which is empty except
   143      * for a key of {@code "TESTCASE"}.
   144      * Subclasses will typically call {@code setSrc(TestSource)}
   145      * to setup a useful test-case template.
   146      */
   147     public Tester() {
   148         this.testCase = this.getClass().getName();
   149         src = new TestSource("TESTCASE");
   150     }
   152     /**
   153      * Set the top-level source template.
   154      */
   155     protected Tester setSrc(TestSource src) {
   156         this.src = src;
   157         return this;
   158     }
   160     /**
   161      * Convenience method for calling {@code innerSrc("TESTCASE", ...)}.
   162      */
   163     protected Tester setSrc(String... lines) {
   164         return innerSrc("TESTCASE", lines);
   165     }
   167     /**
   168      * Convenience method for calling {@code innerSrc(key, new TestSource(...))}.
   169      */
   170     protected Tester innerSrc(String key, String... lines) {
   171         return innerSrc(key, new TestSource(lines));
   172     }
   174     /**
   175      * Specialize the testcase template, setting replacement content
   176      * for the specified key.
   177      */
   178     protected Tester innerSrc(String key, TestSource content) {
   179         if (src == null) {
   180             src = new TestSource(key);
   181         }
   182         src.setInner(key, content);
   183         return this;
   184     }
   186     /**
   187      * On the first invocation, call {@code execute()} to compile
   188      * the test-case source and process the resulting class(se)
   189      * into verifiable output.
   190      * <p>
   191      * Verify that the output matches each of the regular expressions
   192      * given as argument.
   193      * <p>
   194      * Any failure to match constitutes a test failure, but doesn't
   195      * abort the test-case.
   196      * <p>
   197      * Any exception (e.g. bad regular expression syntax) results in
   198      * a test failure, and aborts the test-case.
   199      */
   200     protected void verify(String... expect) {
   201         if (!didExecute) {
   202             try {
   203                 execute();
   204             } catch(Exception ue) {
   205                 throw new Error(ue);
   206             } finally {
   207                 didExecute = true;
   208             }
   209         }
   210         if (output == null) {
   211             error("output is null");
   212             return;
   213         }
   214         for (String e: expect) {
   215             // Escape regular expressions (to allow input to be literals).
   216             // Notice, characters to be escaped are themselves identified
   217             // using regular expressions
   218             String rc[] = { "(", ")", "[", "]", "{", "}", "$" };
   219             for (String c : rc) {
   220                 e = e.replace(c, "\\" + c);
   221             }
   222             // DEBUG: Uncomment this to test modulo constant pool index.
   223             // e = e.replaceAll("#[0-9]{2}", "#[0-9]{2}");
   224             if (!output.matches("(?s).*" + e + ".*")) {
   225                 if (!didPrint) {
   226                     out.println(output);
   227                     didPrint = true;
   228                 }
   229                 error("not matched: '" + e + "'");
   230             } else if(debug) {
   231                 out.println("matched: '" + e + "'");
   232             }
   233         }
   234     }
   236     /**
   237      * Calls {@code writeTestFile()} to write out the test-case source
   238      * content to a file, then call {@code compileTestFile()} to
   239      * compile it, and finally run the {@link process} method to produce
   240      * verifiable output. The default {@code process} method runs javap.
   241      * <p>
   242      * If an exception occurs, it results in a test failure, and
   243      * aborts the test-case.
   244      */
   245     protected void execute() throws IOException {
   246         err.println("TestCase: " + testCase);
   247         writeTestFile();
   248         compileTestFile();
   249         process();
   250     }
   252     /**
   253      * Generate java source from test-case.
   254      * TBD: change to use javaFileObject, possibly make
   255      * this class extend JavaFileObject.
   256      */
   257     protected void writeTestFile() throws IOException {
   258         javaFile = new File("Test.java");
   259         FileWriter fw = new FileWriter(javaFile);
   260         BufferedWriter bw = new BufferedWriter(fw);
   261         PrintWriter pw = new PrintWriter(bw);
   262         for (String line : src) {
   263             pw.println(line);
   264             if (debug) out.println(line);
   265         }
   266         pw.close();
   267     }
   269     /**
   270      * Compile the Java source code.
   271      */
   272     protected void compileTestFile() {
   273         String path = javaFile.getPath();
   274         String params[] =  { "-source", "1.8", "-g", path };
   275         int rc = com.sun.tools.javac.Main.compile(params);
   276         if (rc != 0)
   277             throw new Error("compilation failed. rc=" + rc);
   278         classFile = new File(path.substring(0, path.length() - 5) + ".class");
   279     }
   282     /**
   283      * Process class file to generate output for verification.
   284      * The default implementation simply runs javap. This might be
   285      * overwritten to generate output in a different manner.
   286      */
   287     protected void process() {
   288         String testClasses = "."; //System.getProperty("test.classes", ".");
   289         StringWriter sw = new StringWriter();
   290         PrintWriter pw = new PrintWriter(sw);
   291         String[] args = { "-v", "-classpath", testClasses, "Test" };
   292         int rc = com.sun.tools.javap.Main.run(args, pw);
   293         if (rc != 0)
   294             throw new Error("javap failed. rc=" + rc);
   295         pw.close();
   296         output = sw.toString();
   297         if (debug) {
   298             out.println(output);
   299             didPrint = true;
   300         }
   302     }
   305     private String testCase;
   306     private TestSource src;
   307     private File javaFile = null;
   308     private File classFile = null;
   309     private String output = null;
   310     private boolean didExecute = false;
   311     private boolean didPrint = false;
   314     protected void error(String msg) {
   315         err.println("Error: " + msg);
   316         errors++;
   317     }
   319     private int cases;
   320     private int fcases;
   321     private int errors;
   322     private int ignored;
   324     /**
   325      * The TestSource class provides a simple container for
   326      * test cases. It contains an array of source code lines,
   327      * where zero or more lines may be markers for nested lines.
   328      * This allows representing templates, with specialization.
   329      * <P>
   330      * This may be generalized to support more advance combo
   331      * tests, but presently it's only used with a static template,
   332      * and one level of specialization.
   333      */
   334     public class TestSource implements Iterable<String> {
   336         private String[] lines;
   337         private Hashtable<String, TestSource> innerSrc;
   339         public TestSource(String... lines) {
   340             this.lines = lines;
   341             innerSrc = new Hashtable<String, TestSource>();
   342         }
   344         public void setInner(String key, TestSource inner) {
   345             innerSrc.put(key, inner);
   346         }
   348         public void setInner(String key, String... lines) {
   349             innerSrc.put(key, new TestSource(lines));
   350         }
   352         public Iterator<String> iterator() {
   353             return new LineIterator();
   354         }
   356         private class LineIterator implements Iterator<String> {
   358             int nextLine = 0;
   359             Iterator<String> innerIt = null;
   361             public  boolean hasNext() {
   362                 return nextLine < lines.length;
   363             }
   365             public String next() {
   366                 if (!hasNext()) throw new NoSuchElementException();
   367                 String str = lines[nextLine];
   368                 TestSource inner = innerSrc.get(str);
   369                 if (inner == null) {
   370                     nextLine++;
   371                     return str;
   372                 }
   373                 if (innerIt == null) {
   374                     innerIt = inner.iterator();
   375                 }
   376                 if (innerIt.hasNext()) {
   377                     return innerIt.next();
   378                 }
   379                 innerIt = null;
   380                 nextLine++;
   381                 return next();
   382             }
   384             public void remove() {
   385                 throw new UnsupportedOperationException();
   386             }
   387         }
   388     }
   389 }

mercurial