|
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 */ |
|
23 |
|
24 import java.io.*; |
|
25 import java.util.*; |
|
26 import java.lang.annotation.*; |
|
27 import java.lang.reflect.InvocationTargetException; |
|
28 |
|
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 { |
|
49 |
|
50 private static boolean debug = false; |
|
51 private static final PrintStream out = System.err; |
|
52 private static final PrintStream err = System.err; |
|
53 |
|
54 |
|
55 protected void run(String... args) throws Exception { |
|
56 |
|
57 final File classesdir = new File(System.getProperty("test.classes", ".")); |
|
58 |
|
59 String[] classNames = args; |
|
60 |
|
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 } |
|
112 |
|
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 } |
|
119 |
|
120 |
|
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 { } |
|
128 |
|
129 /** |
|
130 * Individual test-cases failing due to product bugs, may temporarily |
|
131 * be excluded by marking them like this: |
|
132 * @ignore // 1234567:bug synopsis |
|
133 */ |
|
134 @Retention(RetentionPolicy.RUNTIME) |
|
135 @interface ignore { } |
|
136 |
|
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 } |
|
151 |
|
152 /** |
|
153 * Set the top-level source template. |
|
154 */ |
|
155 protected Tester setSrc(TestSource src) { |
|
156 this.src = src; |
|
157 return this; |
|
158 } |
|
159 |
|
160 /** |
|
161 * Convenience method for calling {@code innerSrc("TESTCASE", ...)}. |
|
162 */ |
|
163 protected Tester setSrc(String... lines) { |
|
164 return innerSrc("TESTCASE", lines); |
|
165 } |
|
166 |
|
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 } |
|
173 |
|
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 } |
|
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 */ |
|
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 } |
|
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 */ |
|
245 protected void execute() throws IOException { |
|
246 err.println("TestCase: " + testCase); |
|
247 writeTestFile(); |
|
248 compileTestFile(); |
|
249 process(); |
|
250 } |
|
251 |
|
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 } |
|
268 |
|
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 } |
|
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 */ |
|
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 } |
|
301 |
|
302 } |
|
303 |
|
304 |
|
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; |
|
312 |
|
313 |
|
314 protected void error(String msg) { |
|
315 err.println("Error: " + msg); |
|
316 errors++; |
|
317 } |
|
318 |
|
319 private int cases; |
|
320 private int fcases; |
|
321 private int errors; |
|
322 private 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 */ |
|
334 public class TestSource implements Iterable<String> { |
|
335 |
|
336 private String[] lines; |
|
337 private Hashtable<String, TestSource> innerSrc; |
|
338 |
|
339 public TestSource(String... lines) { |
|
340 this.lines = lines; |
|
341 innerSrc = new Hashtable<String, TestSource>(); |
|
342 } |
|
343 |
|
344 public void setInner(String key, TestSource inner) { |
|
345 innerSrc.put(key, inner); |
|
346 } |
|
347 |
|
348 public void setInner(String key, String... lines) { |
|
349 innerSrc.put(key, new TestSource(lines)); |
|
350 } |
|
351 |
|
352 public Iterator<String> iterator() { |
|
353 return new LineIterator(); |
|
354 } |
|
355 |
|
356 private class LineIterator implements Iterator<String> { |
|
357 |
|
358 int nextLine = 0; |
|
359 Iterator<String> innerIt = null; |
|
360 |
|
361 public boolean hasNext() { |
|
362 return nextLine < lines.length; |
|
363 } |
|
364 |
|
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 } |
|
383 |
|
384 public void remove() { |
|
385 throw new UnsupportedOperationException(); |
|
386 } |
|
387 } |
|
388 } |
|
389 } |