jdk
1/*
2* Copyright (c) 2013, 2016, 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
24import java.io.*;25import java.util.*;26import java.lang.annotation.*;27import java.lang.reflect.InvocationTargetException;28
29/**
30* {@code JavapTester} 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*/
48public abstract class JavapTester {49
50private static boolean debug = false;51private static final PrintStream out = System.err;52private static final PrintStream err = System.err;53
54
55protected void run(String... args) throws Exception {56
57final File classesdir = new File(System.getProperty("test.classes", "."));58
59String[] classNames = args;60
61// If no test-cases are specified, we regard all co-located classes62// as potential test-cases.63if (args.length == 0) {64final String pattern = ".*\\.class";65final File classFiles[] = classesdir.listFiles(new FileFilter() {66public boolean accept(File f) {67return f.getName().matches(pattern);68}69});70ArrayList<String> names = new ArrayList<String>(classFiles.length);71for (File f : classFiles) {72String fname = f.getName();73names.add(fname.substring(0, fname.length() -6));74}75classNames = names.toArray(new String[names.size()]);76} else {77debug = true;78}79// Test-cases must extend the driver type, and be marked80// @TestCase. Other arguments (classes) are ignored.81// Test-cases are instantiated, and thereby executed.82for (String clname : classNames) {83try {84final Class tclass = Class.forName(clname);85if (!getClass().isAssignableFrom(tclass)) continue;86TestCase anno = (TestCase) tclass.getAnnotation(TestCase.class);87if (anno == null) continue;88if (!debug) {89ignore i = (ignore) tclass.getAnnotation(ignore.class);90if (i != null) {91out.println("Ignore: " + clname);92ignored++;93continue;94}95}96out.println("TestCase: " + clname);97cases++;98JavapTester tc = (JavapTester) tclass.getConstructor().newInstance();99if (tc.errors > 0) {100error("" + tc.errors + " test points failed in " + clname);101errors += tc.errors - 1;102fcases++;103}104} catch(ReflectiveOperationException roe) {105error("Warning: " + clname + " - ReflectiveOperationException");106roe.printStackTrace(err);107} catch(Exception unknown) {108error("Warning: " + clname + " - uncaught exception");109unknown.printStackTrace(err);110}111}112
113String imsg = ignored > 0 ? " (" + ignored + " ignored)" : "";114if (errors > 0)115throw new Error(errors + " error, in " + fcases + " of " + cases + " test-cases" + imsg);116else117err.println("" + cases + " test-cases executed" + imsg + ", no errors");118}119
120
121/**122* Test-cases must be marked with the {@code TestCase} annotation,
123* as well as extend {@code JavapTester} (or an driver extension
124* specified as the first argument to the {@code main()} method.
125*/
126@Retention(RetentionPolicy.RUNTIME)127@interface TestCase { }128
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 { }136
137/**138* Test-cases are classes extending {@code JavapTester}, 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*/
147public JavapTester() {148this.testCase = this.getClass().getName();149src = new TestSource("TESTCASE");150}151
152/**153* Set the top-level source template.
154*/
155protected JavapTester setSrc(TestSource src) {156this.src = src;157return this;158}159
160/**161* Convenience method for calling {@code innerSrc("TESTCASE", ...)}.
162*/
163protected JavapTester setSrc(String... lines) {164return innerSrc("TESTCASE", lines);165}166
167/**168* Convenience method for calling {@code innerSrc(key, new TestSource(...))}.
169*/
170protected JavapTester innerSrc(String key, String... lines) {171return innerSrc(key, new TestSource(lines));172}173
174/**175* Specialize the testcase template, setting replacement content
176* for the specified key.
177*/
178protected JavapTester innerSrc(String key, TestSource content) {179if (src == null) {180src = new TestSource(key);181}182src.setInner(key, content);183return this;184}185
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*/
200protected void verify(String... expect) {201if (!didExecute) {202try {203execute();204} catch(Exception ue) {205throw new Error(ue);206} finally {207didExecute = true;208}209}210if (output == null) {211error("output is null");212return;213}214for (String e: expect) {215// Escape regular expressions (to allow input to be literals).216// Notice, characters to be escaped are themselves identified217// using regular expressions218String rc[] = { "(", ")", "[", "]", "{", "}", "$" };219for (String c : rc) {220e = 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}");224if (!output.matches("(?s).*" + e + ".*")) {225if (!didPrint) {226out.println(output);227didPrint = true;228}229error("not matched: '" + e + "'");230} else if(debug) {231out.println("matched: '" + e + "'");232}233}234}235
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*/
245protected void execute() throws IOException {246err.println("TestCase: " + testCase);247writeTestFile();248compileTestFile();249process();250}251
252/**253* Generate java source from test-case.
254* TBD: change to use javaFileObject, possibly make
255* this class extend JavaFileObject.
256*/
257protected void writeTestFile() throws IOException {258javaFile = new File("Test.java");259FileWriter fw = new FileWriter(javaFile);260BufferedWriter bw = new BufferedWriter(fw);261PrintWriter pw = new PrintWriter(bw);262for (String line : src) {263pw.println(line);264if (debug) out.println(line);265}266pw.close();267}268
269/**270* Compile the Java source code.
271*/
272protected void compileTestFile() {273String path = javaFile.getPath();274String params[] = {"-g", path };275int rc = com.sun.tools.javac.Main.compile(params);276if (rc != 0)277throw new Error("compilation failed. rc=" + rc);278classFile = new File(path.substring(0, path.length() - 5) + ".class");279}280
281
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*/
287protected void process() {288String testClasses = "."; //System.getProperty("test.classes", ".");289StringWriter sw = new StringWriter();290PrintWriter pw = new PrintWriter(sw);291String[] args = { "-v", "-classpath", testClasses, "Test" };292int rc = com.sun.tools.javap.Main.run(args, pw);293if (rc != 0)294throw new Error("javap failed. rc=" + rc);295pw.close();296output = sw.toString();297if (debug) {298out.println(output);299didPrint = true;300}301
302}303
304
305private String testCase;306private TestSource src;307private File javaFile = null;308private File classFile = null;309private String output = null;310private boolean didExecute = false;311private boolean didPrint = false;312
313
314protected void error(String msg) {315err.println("Error: " + msg);316errors++;317}318
319private int cases;320private int fcases;321private int errors;322private int ignored;323
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*/
334public class TestSource implements Iterable<String> {335
336private String[] lines;337private Hashtable<String, TestSource> innerSrc;338
339public TestSource(String... lines) {340this.lines = lines;341innerSrc = new Hashtable<String, TestSource>();342}343
344public void setInner(String key, TestSource inner) {345innerSrc.put(key, inner);346}347
348public void setInner(String key, String... lines) {349innerSrc.put(key, new TestSource(lines));350}351
352public Iterator<String> iterator() {353return new LineIterator();354}355
356private class LineIterator implements Iterator<String> {357
358int nextLine = 0;359Iterator<String> innerIt = null;360
361public boolean hasNext() {362return nextLine < lines.length;363}364
365public String next() {366if (!hasNext()) throw new NoSuchElementException();367String str = lines[nextLine];368TestSource inner = innerSrc.get(str);369if (inner == null) {370nextLine++;371return str;372}373if (innerIt == null) {374innerIt = inner.iterator();375}376if (innerIt.hasNext()) {377return innerIt.next();378}379innerIt = null;380nextLine++;381return next();382}383
384public void remove() {385throw new UnsupportedOperationException();386}387}388}389}
390