19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
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 |
20 * or visit www.oracle.com if you need additional information or have any |
21 * questions. |
21 * questions. |
22 */ |
22 */ |
23 |
23 |
24 import java.io.*; |
24 import java.io.BufferedWriter; |
|
25 import java.io.ByteArrayOutputStream; |
|
26 import java.io.File; |
|
27 import java.io.FileNotFoundException; |
|
28 import java.io.FileWriter; |
|
29 import java.io.FilenameFilter; |
|
30 import java.io.IOException; |
|
31 import java.io.PrintStream; |
|
32 import java.io.PrintWriter; |
|
33 import java.io.StringWriter; |
|
34 import java.lang.annotation.Annotation; |
|
35 import java.lang.annotation.Retention; |
|
36 import java.lang.annotation.RetentionPolicy; |
|
37 import java.lang.ref.SoftReference; |
|
38 import java.lang.reflect.InvocationTargetException; |
|
39 import java.lang.reflect.Method; |
|
40 import java.nio.file.Files; |
|
41 import java.util.ArrayList; |
|
42 import java.util.EnumMap; |
|
43 import java.util.HashMap; |
|
44 import java.util.List; |
|
45 import java.util.Map; |
25 |
46 |
26 |
47 |
27 /** |
48 /** |
28 * Runs javadoc and then runs regression tests on the resulting output. |
49 * Test framework for running javadoc and performing tests on the resulting output. |
29 * This class currently contains three tests: |
50 * |
30 * <ul> |
51 * <p> |
31 * <li> String search: Reads each file, complete with newlines, |
52 * Tests are typically written as subtypes of JavadocTester, with a main |
32 * into a string. Lets you search for strings that contain |
53 * method that creates an instance of the test class and calls the runTests() |
33 * newlines. String matching is case-sensitive. |
54 * method. The runTests() methods calls all the test methods declared in the class, |
34 * You can run javadoc multiple times with different arguments, |
55 * and then calls a method to print a summary, and throw an exception if |
35 * generating output into different destination directories, and |
56 * any of the test methods reported a failure. |
36 * then perform a different array of tests on each one. |
57 * |
37 * To do this, the run method accepts a test array for testing |
58 * <p> |
38 * that a string is found, and a negated test array for testing |
59 * Test methods are identified with a @Test annotation. They have no parameters. |
39 * that a string is not found. |
60 * The name of the method is not important, but if you have more than one, it is |
40 * <li> Run diffs: Iterate through the list of given file pairs |
61 * recommended that the names be meaningful and suggestive of the test case |
41 * and diff the pairs. |
62 * contained therein. |
42 * <li> Check exit code: Check the exit code of Javadoc and |
63 * |
43 * record whether the test passed or failed. |
64 * <p> |
44 * </ul> |
65 * Typically, a test method will invoke javadoc, and then perform various |
|
66 * checks on the results. The standard checks are: |
|
67 * |
|
68 * <dl> |
|
69 * <dt>checkExitCode |
|
70 * <dd>Check the exit code returned from javadoc. |
|
71 * <dt>checkOutput |
|
72 * <dd>Perform a series of checks on the contents on a file or output stream |
|
73 * generated by javadoc. |
|
74 * The checks can be either that a series of strings are found or are not found. |
|
75 * <dt>checkFiles |
|
76 * <dd>Perform a series of checks on the files generated by javadoc. |
|
77 * The checks can be that a series of files are found or are not found. |
|
78 * </dl> |
|
79 * |
|
80 * <pre><code> |
|
81 * public class MyTester extends JavadocTester { |
|
82 * public static void main(String... args) throws Exception { |
|
83 * MyTester tester = new MyTester(); |
|
84 * tester.runTests(); |
|
85 * } |
|
86 * |
|
87 * // test methods... |
|
88 * @Test |
|
89 * void test() { |
|
90 * javadoc(<i>args</i>); |
|
91 * checkExit(Exit.OK); |
|
92 * checkOutput(<i>file</i>, true, |
|
93 * <i>strings-to-find</i>); |
|
94 * checkOutput(<i>file</i>, false, |
|
95 * <i>strings-to-not-find</i>); |
|
96 * } |
|
97 * } |
|
98 * </code></pre> |
|
99 * |
|
100 * <p> |
|
101 * If javadoc is run more than once in a test method, you can compare the |
|
102 * results that are generated with the diff method. Since files written by |
|
103 * javadoc typically contain a timestamp, you may want to use the -notimestamp |
|
104 * option if you are going to compare the results from two runs of javadoc. |
|
105 * |
|
106 * <p> |
|
107 * If you have many calls of checkOutput that are very similar, you can write |
|
108 * your own check... method to reduce the amount of duplication. For example, |
|
109 * if you want to check that many files contain the same string, you could |
|
110 * write a method that takes a varargs list of files and calls checkOutput |
|
111 * on each file in turn with the string to be checked. |
|
112 * |
|
113 * <p> |
|
114 * You can also write you own custom check methods, which can use |
|
115 * readFile to get the contents of a file generated by javadoc, |
|
116 * and then use pass(...) or fail(...) to report whether the check |
|
117 * succeeded or not. |
|
118 * |
|
119 * <p> |
|
120 * You can have many separate test methods, each identified with a @Test |
|
121 * annotation. However, you should <b>not</b> assume they will be called |
|
122 * in the order declared in your source file. If the order of a series |
|
123 * of javadoc invocations is important, do that within a single method. |
|
124 * If the invocations are independent, for better clarity, use separate |
|
125 * test methods, each with their own set of checks on the results. |
45 * |
126 * |
46 * @author Doug Kramer |
127 * @author Doug Kramer |
47 * @author Jamie Ho |
128 * @author Jamie Ho |
48 * @since 1.4.2 |
129 * @author Jonathan Gibbons (rewrite) |
49 */ |
130 */ |
50 public abstract class JavadocTester { |
131 public abstract class JavadocTester { |
51 |
132 |
52 protected static final String NL = System.getProperty("line.separator"); |
133 public static final String FS = System.getProperty("file.separator"); |
53 protected static final String FS = System.getProperty("file.separator"); |
134 public static final String PS = System.getProperty("path.separator"); |
54 |
135 public static final String NL = System.getProperty("line.separator"); |
55 protected static final String SRC_DIR = System.getProperty("test.src", "."); |
136 |
56 protected static final String JAVA_VERSION = System.getProperty("java.version"); |
137 public enum Output { |
57 protected static final String OUTPUT_DIR = "out"; |
138 /** The name for error output from javadoc. */ |
58 protected static final String[][] NO_TEST = new String[][] {}; |
139 ERROR, |
59 protected static final String[] NO_FILE_TEST = new String[] {}; |
140 /** The name for the notice output from javadoc. */ |
60 |
141 NOTICE, |
61 /** |
142 /** The name for the warning output from javadoc. */ |
62 * Use this as the file name in the test array when you want to search |
143 WARNING, |
63 * for a string in the error output. |
144 /** The name for any output written to System.out. */ |
64 */ |
145 STDOUT, |
65 public static final String ERROR_OUTPUT = "ERROR_OUTPUT"; |
146 /** The name for any output written to System.err. */ |
66 |
147 STDERR |
67 /** |
148 } |
68 * Use this as the file name in the test array when you want to search |
149 |
69 * for a string in the notice output. |
150 /** The output directory used in the most recent call of javadoc. */ |
70 */ |
151 protected File outputDir; |
71 public static final String NOTICE_OUTPUT = "NOTICE_OUTPUT"; |
152 |
72 |
153 /** The exit code of the most recent call of javadoc. */ |
73 /** |
154 private int exitCode; |
74 * Use this as the file name in the test array when you want to search |
155 |
75 * for a string in the warning output. |
156 /** The output generated by javadoc to the various writers and streams. */ |
76 */ |
157 private final Map<Output, String> outputMap = new EnumMap<>(Output.class); |
77 public static final String WARNING_OUTPUT = "WARNING_OUTPUT"; |
158 |
78 |
159 /** A cache of file content, to avoid reading files unnecessarily. */ |
79 /** |
160 private final Map<File,SoftReference<String>> fileContentCache = new HashMap<>(); |
80 * Use this as the file name in the test array when you want to search |
161 |
81 * for a string in standard output. |
162 /** Stream used for logging messages. */ |
82 */ |
163 private final PrintStream out = System.out; |
83 public static final String STANDARD_OUTPUT = "STANDARD_OUTPUT"; |
164 |
84 |
165 /** The directory containing the source code for the test. */ |
85 /** |
166 public static final String testSrc = System.getProperty("test.src"); |
86 * The default doclet. |
167 |
87 */ |
168 /** |
88 public static final String DEFAULT_DOCLET_CLASS = "com.sun.tools.doclets.formats.html.HtmlDoclet"; |
169 * Get the path for a source file in the test source directory. |
89 public static final String DEFAULT_DOCLET_CLASS_OLD = "com.sun.tools.doclets.standard.Standard"; |
170 * @param path the path of a file or directory in the source directory |
90 |
171 * @return the full path of the specified file |
91 /** |
172 */ |
92 * The writer to write error messages. |
173 public static String testSrc(String path) { |
93 */ |
174 return new File(testSrc, path).getPath(); |
94 public StringWriter errors; |
175 } |
95 |
|
96 /** |
|
97 * The writer to write notices. |
|
98 */ |
|
99 public StringWriter notices; |
|
100 |
|
101 /** |
|
102 * The writer to write warnings. |
|
103 */ |
|
104 public StringWriter warnings; |
|
105 |
|
106 /** |
|
107 * The buffer of warning output. |
|
108 */ |
|
109 public StringBuffer standardOut; |
|
110 |
|
111 /** |
|
112 * The output directory. |
|
113 */ |
|
114 private File outputDir; |
|
115 |
176 |
116 /** |
177 /** |
117 * Alternatives for checking the contents of a directory. |
178 * Alternatives for checking the contents of a directory. |
118 */ |
179 */ |
119 enum DirectoryCheck { |
180 public enum DirectoryCheck { |
120 /** |
181 /** |
121 * Check that the directory is empty. |
182 * Check that the directory is empty. |
122 */ |
183 */ |
123 EMPTY((file, name) -> true), |
184 EMPTY((file, name) -> true), |
124 /** |
185 /** |
151 } |
212 } |
152 } |
213 } |
153 |
214 |
154 private DirectoryCheck outputDirectoryCheck = DirectoryCheck.EMPTY; |
215 private DirectoryCheck outputDirectoryCheck = DirectoryCheck.EMPTY; |
155 |
216 |
156 /** |
217 /** The current subtest number. Incremented when checking(...) is called. */ |
157 * The current subtest number. |
218 private int numTestsRun = 0; |
158 */ |
219 |
159 private static int numTestsRun = 0; |
220 /** The number of subtests passed. Incremented when passed(...) is called. */ |
160 |
221 private int numTestsPassed = 0; |
161 /** |
222 |
162 * The number of subtests passed. |
223 /** The current run of javadoc. Incremented when javadoc is called. */ |
163 */ |
224 private int javadocRunNum = 0; |
164 private static int numTestsPassed = 0; |
225 |
165 |
226 /** The name of the standard doclet. */ |
166 /** |
227 // This ought not to be necessary; there ought to be a javadoc entry point |
167 * The current run of javadoc |
228 // that does not require this to be know externally. |
168 */ |
229 private static final String standardDocletClassName = |
169 private static int javadocRunNum = 0; |
230 "com.sun.tools.doclets.standard.Standard"; |
170 |
231 |
171 /** |
232 /** Marker annotation for test methods to be invoked by runTests. */ |
172 * Construct a JavadocTester. |
233 @Retention(RetentionPolicy.RUNTIME) |
173 */ |
234 @interface Test { } |
174 public JavadocTester() { |
235 |
175 } |
236 /** |
176 |
237 * Run all methods annotated with @Test, followed by printSummary. |
177 /** |
238 * Typically called on a tester object in main() |
178 * Execute the tests. |
239 * @throws Exception if any errors occurred |
179 * |
240 */ |
180 * @param args the arguments to pass to Javadoc |
241 public void runTests() throws Exception { |
181 * @param testArray the array of tests |
242 for (Method m: getClass().getDeclaredMethods()) { |
182 * @param negatedTestArray the array of negated tests |
243 Annotation a = m.getAnnotation(Test.class); |
183 * @return the return code for the execution of Javadoc |
244 if (a != null) { |
184 */ |
245 try { |
185 public int run(String[] args, |
246 out.println("Running test " + m.getName()); |
186 String[][] testArray, String[][] negatedTestArray) { |
247 m.invoke(this, new Object[] { }); |
187 int returnCode = runJavadoc(args); |
248 } catch (InvocationTargetException e) { |
188 runTestsOnHTML(testArray, negatedTestArray); |
249 Throwable cause = e.getCause(); |
189 return returnCode; |
250 throw (cause instanceof Exception) ? ((Exception) cause) : e; |
190 } |
251 } |
191 |
252 out.println(); |
192 /** |
253 } |
193 * Execute the tests. |
254 } |
194 * |
255 printSummary(); |
195 * @param args the arguments to pass to Javadoc |
256 } |
196 * @param testArray the array of tests |
257 |
197 * @param negatedTestArray the array of negated tests |
258 /** |
198 * @param fileTestArray the array of file tests |
259 * Run javadoc. |
199 * @param negatedFileTestArray the array of negated file tests |
260 * The output directory used by this call and the final exit code |
200 * @return the return code for the execution of Javadoc |
261 * will be saved for later use. |
201 */ |
262 * To aid the reader, it is recommended that calls to this method |
202 public int run(String[] args, |
263 * put each option and the arguments it takes on a separate line. |
203 String[][] testArray, String[][] negatedTestArray, |
264 * |
204 String[] fileTestArray, String[] negatedFileTestArray) { |
265 * Example: |
205 int returnCode = runJavadoc(args); |
266 * <pre><code> |
206 runTestsOnHTML(testArray, negatedTestArray); |
267 * javadoc("-d", "out", |
207 runTestsOnFile(fileTestArray, negatedFileTestArray); |
268 * "-sourcepath", testSrc, |
208 return returnCode; |
269 * "-notimestamp", |
209 } |
270 * "pkg1", "pkg2", "pkg3/C.java"); |
210 |
271 * </code></pre> |
211 /** |
272 * |
212 * Execute Javadoc using the default doclet. |
273 * @param args the arguments to pass to javadoc |
213 * |
274 */ |
214 * @param args the arguments to pass to Javadoc |
275 public void javadoc(String... args) { |
215 * @return the return code from the execution of Javadoc |
276 outputMap.clear(); |
216 */ |
277 fileContentCache.clear(); |
217 public int runJavadoc(String[] args) { |
278 |
218 float javaVersion = Float.parseFloat(JAVA_VERSION.substring(0,3)); |
|
219 String docletClass = javaVersion < 1.5 ? |
|
220 DEFAULT_DOCLET_CLASS_OLD : DEFAULT_DOCLET_CLASS; |
|
221 return runJavadoc(docletClass, args); |
|
222 } |
|
223 |
|
224 |
|
225 /** |
|
226 * Execute Javadoc. |
|
227 * |
|
228 * @param docletClass the doclet being tested. |
|
229 * @param args the arguments to pass to Javadoc |
|
230 * @return the return code from the execution of Javadoc |
|
231 */ |
|
232 public int runJavadoc(String docletClass, String[] args) { |
|
233 javadocRunNum++; |
279 javadocRunNum++; |
234 if (javadocRunNum == 1) { |
280 if (javadocRunNum == 1) { |
235 System.out.println("\n" + "Running javadoc..."); |
281 out.println("Running javadoc..."); |
236 } else { |
282 } else { |
237 System.out.println("\n" + "Running javadoc (run " |
283 out.println("Running javadoc (run " |
238 + javadocRunNum + ")..."); |
284 + javadocRunNum + ")..."); |
239 } |
285 } |
240 initOutputBuffers(); |
|
241 outputDir = new File("."); |
286 outputDir = new File("."); |
242 for (int i = 0; i < args.length - 2; i++) { |
287 for (int i = 0; i < args.length - 2; i++) { |
243 if (args[i].equals("-d")) { |
288 if (args[i].equals("-d")) { |
244 outputDir = new File(args[++i]); |
289 outputDir = new File(args[++i]); |
245 break; |
290 break; |
246 } |
291 } |
247 } |
292 } |
|
293 // log.setOutDir(outputDir); |
248 |
294 |
249 outputDirectoryCheck.check(outputDir); |
295 outputDirectoryCheck.check(outputDir); |
250 |
296 |
251 ByteArrayOutputStream stdout = new ByteArrayOutputStream(); |
297 // These are the primary streams used by javadoc |
252 PrintStream prevOut = System.out; |
298 WriterOutput errOut = new WriterOutput(); |
253 System.setOut(new PrintStream(stdout)); |
299 WriterOutput warnOut = new WriterOutput(); |
254 |
300 WriterOutput noticeOut = new WriterOutput(); |
255 ByteArrayOutputStream stderr = new ByteArrayOutputStream(); |
301 // These are to catch output to System.out and System.err, |
256 PrintStream prevErr = System.err; |
302 // in case these are used instead of the primary streams |
257 System.setErr(new PrintStream(stderr)); |
303 StreamOutput sysOut = new StreamOutput(System.out, System::setOut); |
258 |
304 StreamOutput sysErr = new StreamOutput(System.err, System::setErr); |
259 int returnCode = com.sun.tools.javadoc.Main.execute( |
305 |
260 "javadoc", |
306 try { |
261 new PrintWriter(errors, true), |
307 exitCode = com.sun.tools.javadoc.Main.execute( |
262 new PrintWriter(warnings, true), |
308 "javadoc", |
263 new PrintWriter(notices, true), |
309 errOut.pw, warnOut.pw, noticeOut.pw, |
264 docletClass, |
310 standardDocletClassName, |
265 getClass().getClassLoader(), |
311 args); |
266 args); |
312 } finally { |
267 System.setOut(prevOut); |
313 outputMap.put(Output.STDOUT, sysOut.close()); |
268 standardOut = new StringBuffer(stdout.toString()); |
314 outputMap.put(Output.STDERR, sysErr.close()); |
269 System.setErr(prevErr); |
315 outputMap.put(Output.ERROR, errOut.close()); |
270 errors.write(NL + stderr.toString()); |
316 outputMap.put(Output.WARNING, warnOut.close()); |
271 |
317 outputMap.put(Output.NOTICE, noticeOut.close()); |
272 printJavadocOutput(); |
318 } |
273 return returnCode; |
319 |
274 } |
320 outputMap.forEach((name, text) -> { |
275 |
321 if (!text.isEmpty()) { |
276 /** |
322 out.println("javadoc " + name + ":"); |
277 * Set a filter to check the initial contents of the output directory |
323 out.println(text); |
|
324 } |
|
325 }); |
|
326 } |
|
327 |
|
328 /** |
|
329 * Set the kind of check for the initial contents of the output directory |
278 * before javadoc is run. |
330 * before javadoc is run. |
279 * The filter should return true for files that should <b>not</b> appear. |
331 * The filter should return true for files that should <b>not</b> appear. |
280 */ |
332 * @param c the kind of check to perform |
281 public void setCheckOutputDirectoryCheck(DirectoryCheck c) { |
333 */ |
|
334 public void setOutputDirectoryCheck(DirectoryCheck c) { |
282 outputDirectoryCheck = c; |
335 outputDirectoryCheck = c; |
283 } |
336 } |
284 |
337 |
285 /** |
338 public enum Exit { |
286 * Create new string writer buffers |
339 OK(0), |
287 */ |
340 FAILED(1); |
288 private void initOutputBuffers() { |
341 |
289 errors = new StringWriter(); |
342 Exit(int code) { |
290 notices = new StringWriter(); |
343 this.code = code; |
291 warnings = new StringWriter(); |
344 } |
292 } |
345 |
293 |
346 final int code; |
294 /** |
347 } |
295 * Run array of tests on the resulting HTML. |
348 |
296 * This method accepts a testArray for testing that a string is found |
349 /** |
297 * and a negatedTestArray for testing that a string is not found. |
350 * Check the exit code of the most recent call of javadoc. |
298 * |
351 * |
299 * @param testArray the array of tests |
352 * @param expected the exit code that is required for the test |
300 * @param negatedTestArray the array of negated tests |
353 * to pass. |
301 */ |
354 */ |
302 public void runTestsOnHTML(String[][] testArray, String[][] negatedTestArray) { |
355 public void checkExit(Exit expected) { |
303 runTestsOnHTML(testArray, false); |
356 checking("check exit code"); |
304 runTestsOnHTML(negatedTestArray, true); |
357 if (exitCode == expected.code) { |
305 } |
358 passed("return code " + exitCode); |
306 |
359 } else { |
307 /** |
360 failed("return code " + exitCode +"; expected " + expected.code + " (" + expected + ")"); |
308 * Run array of tests on the generated files. |
361 } |
309 * This method accepts a fileTestArray for testing if a file is generated |
362 } |
310 * and a negatedFileTestArray for testing if a file is not found. |
363 |
311 * The files are relative to the most recent output directory specified |
364 /** |
312 * with -d. |
365 * Check for content in (or not in) the generated output. |
313 * |
366 * Within the search strings, the newline character \n |
314 * @param fileTestArray the array of file tests |
367 * will be translated to the platform newline character sequence. |
315 * @param negatedFileTestArray the array of negated file tests |
368 * @param path a path within the most recent output directory |
316 */ |
369 * or the name of one of the output buffers, identifying |
317 public void runTestsOnFile(String[] fileTestArray, String[] negatedFileTestArray) { |
370 * where to look for the search strings. |
318 runTestsOnFile(outputDir, fileTestArray, false); |
371 * @param expectedFound true if all of the search strings are expected |
319 runTestsOnFile(outputDir, negatedFileTestArray, true); |
372 * to be found, or false if all of the strings are expected to be |
320 } |
373 * not found |
321 |
374 * @param strings the strings to be searched for |
322 /** |
375 */ |
323 * Run the array of tests on the resulting HTML. |
376 public void checkOutput(String path, boolean expectedFound, String... strings) { |
324 * The files are relative to the most recent output directory specified |
377 // Read contents of file |
325 * with -d. |
378 String fileString; |
326 * |
379 try { |
327 * @param testArray the array of tests |
380 fileString = readFile(outputDir, path); |
328 * @param isNegated true if test is negated; false otherwise |
381 } catch (Error e) { |
329 */ |
382 if (!expectedFound) { |
330 private void runTestsOnHTML(String[][] testArray , boolean isNegated) { |
383 failed("Error reading file: " + e); |
331 for (String[] test : testArray) { |
384 return; |
332 numTestsRun++; |
385 } |
333 System.out.print("Running subtest #" + numTestsRun + "... "); |
386 throw e; |
334 // Get string to find |
387 } |
335 String stringToFind = test[1]; |
388 checkOutput(path, fileString, expectedFound, strings); |
336 // Read contents of file into a string |
389 } |
337 String fileString; |
390 |
338 try { |
391 /** |
339 fileString = readFileToString(outputDir, test[0]); |
392 * Check for content in (or not in) the one of the output streams written by |
340 } catch (Error e) { |
393 * javadoc. Within the search strings, the newline character \n |
341 if (isNegated) { |
394 * will be translated to the platform newline character sequence. |
342 System.out.println( "FAILED, due to " + e + "\n"); |
395 * @param output the output stream to check |
343 continue; |
396 * @param expectedFound true if all of the search strings are expected |
344 } |
397 * to be found, or false if all of the strings are expected to be |
345 throw e; |
398 * not found |
346 } |
399 * @param strings the strings to be searched for |
|
400 */ |
|
401 public void checkOutput(Output output, boolean expectedFound, String... strings) { |
|
402 checkOutput(output.toString(), outputMap.get(output), expectedFound, strings); |
|
403 } |
|
404 |
|
405 private void checkOutput(String path, String fileString, boolean expectedFound, String... strings) { |
|
406 for (String stringToFind : strings) { |
|
407 // log.logCheckOutput(path, expectedFound, stringToFind); |
|
408 checking("checkOutput"); |
347 // Find string in file's contents |
409 // Find string in file's contents |
348 boolean isFound = findString(fileString, stringToFind); |
410 boolean isFound = findString(fileString, stringToFind); |
349 if ((isNegated && !isFound) || (!isNegated && isFound)) { |
411 if (isFound == expectedFound) { |
350 numTestsPassed += 1; |
412 passed(path + ": " + (isFound ? "found:" : "not found:") + "\n" |
351 System.out.println("Passed" + "\n" |
413 + stringToFind + "\n"); |
352 + (isNegated ? "not found:" : "found:") + "\n" |
|
353 + stringToFind + " in " + test[0] + "\n"); |
|
354 } else { |
414 } else { |
355 System.out.println("FAILED, when searching for:" + "\n" |
415 failed(path + ": " + (isFound ? "found:" : "not found:") + "\n" |
356 + stringToFind |
416 + stringToFind + "\n"); |
357 + " in " + test[0] + "\n"); |
417 } |
358 } |
418 } |
359 } |
419 } |
360 } |
420 |
361 |
421 /** |
362 /** |
422 * Check for files in (or not in) the generated output. |
363 * Run the array of file tests on the generated files. |
423 * @param expectedFound true if all of the files are expected |
364 * |
424 * to be found, or false if all of the files are expected to be |
365 * @param testArray the array of file tests |
425 * not found |
366 * @param isNegated true if test is negated; false otherwise |
426 * @param paths the files to check, within the most recent output directory. |
367 */ |
427 * */ |
368 private void runTestsOnFile(File baseDir, String[] testArray, boolean isNegated) { |
428 public void checkFiles(boolean expectedFound, String... paths) { |
369 for (String fileName : testArray) { |
429 for (String path: paths) { |
370 numTestsRun++; |
430 // log.logCheckFile(path, expectedFound); |
371 String failedString = "FAILED: file (" + fileName + ") found" + "\n"; |
431 checking("checkFile"); |
372 String passedString = "Passed" + "\n" + |
432 File file = new File(outputDir, path); |
373 "file (" + fileName + ") not found" + "\n"; |
433 boolean isFound = file.exists(); |
374 System.out.print("Running subtest #" + numTestsRun + "... "); |
434 if (isFound == expectedFound) { |
375 try { |
435 passed(path + ": " + (isFound ? "found:" : "not found:") + "\n"); |
376 File file = new File(baseDir, fileName); |
436 } else { |
377 if ((file.exists() && !isNegated) || (!file.exists() && isNegated)) { |
437 failed(path + ": " + (isFound ? "found:" : "not found:") + "\n"); |
378 numTestsPassed += 1; |
438 } |
379 System.out.println(passedString); |
439 } |
380 } else { |
440 } |
381 System.out.println(failedString); |
441 |
382 } |
442 /** |
383 } catch (Error e) { |
443 * Check that a series of strings are found in order in a file in |
384 System.err.println(e); |
444 * the generated output. |
385 } |
445 * @param path the file to check |
386 } |
446 * @param strings the strings whose order to check |
387 } |
447 */ |
388 |
448 public void checkOrder(String path, String... strings) { |
389 /** |
449 String fileString = readOutputFile(path); |
390 * Iterate through the list of given file pairs and diff each file. |
450 int prevIndex = -1; |
|
451 for (String s : strings) { |
|
452 int currentIndex = fileString.indexOf(s); |
|
453 checking(s + " at index " + currentIndex); |
|
454 if (currentIndex >= prevIndex) { |
|
455 passed(s + "is in the correct order"); |
|
456 } else { |
|
457 failed(s + " is in the wrong order."); |
|
458 } |
|
459 prevIndex = currentIndex; |
|
460 } |
|
461 } |
|
462 |
|
463 /** |
|
464 * Compare a set of files in each of two directories. |
391 * |
465 * |
392 * @param baseDir1 the directory containing the first set of files |
466 * @param baseDir1 the directory containing the first set of files |
393 * @param baseDir2 the directory containing the second set of files |
467 * @param baseDir2 the directory containing the second set of files |
394 * @param files the set of files to be compared |
468 * @param files the set of files to be compared |
395 * @throws Error if any differences are found between |
469 */ |
396 * file pairs. |
470 public void diff(String baseDir1, String baseDir2, String... files) { |
397 */ |
|
398 public void runDiffs(String baseDir1, String baseDir2, String[] files) throws Error { |
|
399 runDiffs(baseDir1, baseDir2, files, true); |
|
400 } |
|
401 |
|
402 /** |
|
403 * Iterate through the list of given file pairs and diff each file. |
|
404 * |
|
405 * @param baseDir1 the directory containing the first set of files |
|
406 * @param baseDir2 the directory containing the second set of files |
|
407 * @param files the set of files to be compared |
|
408 * @param throwErrorIfNoMatch flag to indicate whether or not to throw |
|
409 * an error if the files do not match. |
|
410 * |
|
411 * @throws Error if any differences are found between |
|
412 * file pairs and throwErrorIfNoMatch is true. |
|
413 */ |
|
414 public void runDiffs(String baseDir1, String baseDir2, String[] files, boolean throwErrorIfNoMatch) throws Error { |
|
415 File bd1 = new File(baseDir1); |
471 File bd1 = new File(baseDir1); |
416 File bd2 = new File(baseDir2); |
472 File bd2 = new File(baseDir2); |
417 for (String file : files) { |
473 for (String file : files) { |
418 diff(bd1, bd2, file, throwErrorIfNoMatch); |
474 diff(bd1, bd2, file); |
419 } |
475 } |
420 } |
476 } |
421 |
477 |
422 /** |
478 /** |
423 * Check the exit code of Javadoc and record whether the test passed |
479 * A utility to copy a directory from one place to another. |
424 * or failed. |
480 * |
425 * |
481 * @param targetDir the directory to copy. |
426 * @param expectedExitCode The exit code that is required for the test |
482 * @param destDir the destination to copy the directory to. |
427 * to pass. |
483 */ |
428 * @param actualExitCode The actual exit code from the previous run of |
484 // TODO: convert to using java.nio.Files.walkFileTree |
429 * Javadoc. |
485 public void copyDir(String targetDir, String destDir) { |
430 */ |
486 try { |
431 public void checkExitCode(int expectedExitCode, int actualExitCode) { |
487 File targetDirObj = new File(targetDir); |
432 numTestsRun++; |
488 File destDirParentObj = new File(destDir); |
433 if (expectedExitCode == actualExitCode) { |
489 File destDirObj = new File(destDirParentObj, targetDirObj.getName()); |
434 System.out.println( "Passed" + "\n" + " got return code " + |
490 if (! destDirParentObj.exists()) { |
435 actualExitCode); |
491 destDirParentObj.mkdir(); |
436 numTestsPassed++; |
492 } |
437 } else { |
493 if (! destDirObj.exists()) { |
438 System.out.println( "FAILED: expected return code " + |
494 destDirObj.mkdir(); |
439 expectedExitCode + " but got " + actualExitCode); |
495 } |
440 } |
496 String[] files = targetDirObj.list(); |
441 } |
497 for (String file : files) { |
442 |
498 File srcFile = new File(targetDirObj, file); |
443 /** |
499 File destFile = new File(destDirObj, file); |
444 * Print a summary of the test results. |
500 if (srcFile.isFile()) { |
445 */ |
501 out.println("Copying " + srcFile + " to " + destFile); |
446 protected void printSummary() { |
502 copyFile(destFile, srcFile); |
447 if ( numTestsRun != 0 && numTestsPassed == numTestsRun ) { |
503 } else if(srcFile.isDirectory()) { |
448 // Test passed |
504 copyDir(srcFile.getAbsolutePath(), destDirObj.getAbsolutePath()); |
449 System.out.println("\n" + "All " + numTestsPassed |
505 } |
450 + " subtests passed"); |
506 } |
451 } else { |
507 } catch (IOException exc) { |
452 // Test failed |
508 throw new Error("Could not copy " + targetDir + " to " + destDir); |
453 throw new Error("\n" + (numTestsRun - numTestsPassed) |
509 } |
454 + " of " + (numTestsRun) |
510 } |
455 + " subtests failed\n"); |
511 |
456 } |
512 /** |
457 } |
513 * Copy source file to destination file. |
458 |
514 * |
459 /** |
515 * @param destfile the destination file |
460 * Print the output stored in the buffers. |
516 * @param srcfile the source file |
461 */ |
517 * @throws IOException |
462 protected void printJavadocOutput() { |
518 */ |
463 System.out.println(STANDARD_OUTPUT + " : \n" + getStandardOutput()); |
519 public void copyFile(File destfile, File srcfile) throws IOException { |
464 System.err.println(ERROR_OUTPUT + " : \n" + getErrorOutput()); |
520 Files.copy(srcfile.toPath(), destfile.toPath()); |
465 System.err.println(WARNING_OUTPUT + " : \n" + getWarningOutput()); |
521 } |
466 System.out.println(NOTICE_OUTPUT + " : \n" + getNoticeOutput()); |
522 |
467 } |
523 /** |
468 |
524 * Read a file from the output directory. |
469 /** |
|
470 * Read the file and return it as a string. |
|
471 * |
525 * |
472 * @param fileName the name of the file to read |
526 * @param fileName the name of the file to read |
473 * @return the file in string format |
527 * @return the file in string format |
474 */ |
528 */ |
475 public String readFileToString(String fileName) throws Error { |
529 public String readOutputFile(String fileName) throws Error { |
476 return readFileToString(outputDir, fileName); |
530 return readFile(outputDir, fileName); |
|
531 } |
|
532 |
|
533 protected String readFile(String fileName) throws Error { |
|
534 return readFile(outputDir, fileName); |
|
535 } |
|
536 |
|
537 protected String readFile(String baseDir, String fileName) throws Error { |
|
538 return readFile(new File(baseDir), fileName); |
477 } |
539 } |
478 |
540 |
479 /** |
541 /** |
480 * Read the file and return it as a string. |
542 * Read the file and return it as a string. |
481 * |
543 * |
482 * @param baseDir the directory in which to locate the file |
544 * @param baseDir the directory in which to locate the file |
483 * @param fileName the name of the file to read |
545 * @param fileName the name of the file to read |
484 * @return the file in string format |
546 * @return the file in string format |
485 */ |
547 */ |
486 private String readFileToString(File baseDir, String fileName) throws Error { |
548 private String readFile(File baseDir, String fileName) throws Error { |
487 switch (fileName) { |
|
488 case ERROR_OUTPUT: |
|
489 return getErrorOutput(); |
|
490 case NOTICE_OUTPUT: |
|
491 return getNoticeOutput(); |
|
492 case WARNING_OUTPUT: |
|
493 return getWarningOutput(); |
|
494 case STANDARD_OUTPUT: |
|
495 return getStandardOutput(); |
|
496 } |
|
497 try { |
549 try { |
498 File file = new File(baseDir, fileName); |
550 File file = new File(baseDir, fileName); |
499 if ( !file.exists() ) { |
551 SoftReference<String> ref = fileContentCache.get(file); |
500 System.out.println("\n" + "FILE DOES NOT EXIST: " + fileName); |
552 String content = (ref == null) ? null : ref.get(); |
501 } |
553 if (content != null) |
502 char[] allChars; |
554 return content; |
503 try (BufferedReader in = new BufferedReader(new FileReader(file))) { |
555 |
504 // Create an array of characters the size of the file |
556 content = new String(Files.readAllBytes(file.toPath())); |
505 allChars = new char[(int)file.length()]; |
557 fileContentCache.put(file, new SoftReference(content)); |
506 // Read the characters into the allChars array |
558 return content; |
507 in.read(allChars, 0, (int)file.length()); |
|
508 } |
|
509 |
|
510 // Convert to a string |
|
511 String allCharsString = new String(allChars); |
|
512 return allCharsString; |
|
513 } catch (FileNotFoundException e) { |
559 } catch (FileNotFoundException e) { |
514 System.err.println(e); |
560 System.err.println(e); |
515 throw new Error("File not found: " + fileName); |
561 throw new Error("File not found: " + fileName); |
516 } catch (IOException e) { |
562 } catch (IOException e) { |
517 System.err.println(e); |
563 System.err.println(e); |
518 throw new Error("Error reading file: " + fileName); |
564 throw new Error("Error reading file: " + fileName); |
519 } |
565 } |
520 } |
566 } |
521 |
567 |
|
568 protected void checking(String message) { |
|
569 numTestsRun++; |
|
570 print("Starting subtest " + numTestsRun, message); |
|
571 } |
|
572 |
|
573 protected void passed(String message) { |
|
574 numTestsPassed++; |
|
575 print("Passed", message); |
|
576 } |
|
577 |
|
578 protected void failed(String message) { |
|
579 print("FAILED", message); |
|
580 } |
|
581 |
|
582 private void print(String prefix, String message) { |
|
583 if (message.isEmpty()) |
|
584 out.println(prefix); |
|
585 else { |
|
586 out.print(prefix); |
|
587 out.print(": "); |
|
588 out.println(message.replace("\n", NL)); |
|
589 } |
|
590 } |
|
591 |
|
592 /** |
|
593 * Print a summary of the test results. |
|
594 */ |
|
595 protected void printSummary() { |
|
596 // log.write(); |
|
597 if (numTestsRun != 0 && numTestsPassed == numTestsRun) { |
|
598 // Test passed |
|
599 out.println(); |
|
600 out.println("All " + numTestsPassed + " subtests passed"); |
|
601 } else { |
|
602 // Test failed |
|
603 throw new Error((numTestsRun - numTestsPassed) |
|
604 + " of " + (numTestsRun) |
|
605 + " subtests failed"); |
|
606 } |
|
607 } |
|
608 |
|
609 /** |
|
610 * Search for the string in the given file and return true |
|
611 * if the string was found. |
|
612 * |
|
613 * @param fileString the contents of the file to search through |
|
614 * @param stringToFind the string to search for |
|
615 * @return true if the string was found |
|
616 */ |
|
617 private boolean findString(String fileString, String stringToFind) { |
|
618 // javadoc (should) always use the platform newline sequence, |
|
619 // but in the strings to find it is more convenient to use the Java |
|
620 // newline character. So we translate \n to NL before we search. |
|
621 stringToFind = stringToFind.replace("\n", NL); |
|
622 return fileString.contains(stringToFind); |
|
623 } |
|
624 |
522 /** |
625 /** |
523 * Compare the two given files. |
626 * Compare the two given files. |
524 * |
627 * |
525 * @param baseDir1 the directory in which to locate the first file |
628 * @param baseDir1 the directory in which to locate the first file |
526 * @param baseDir2 the directory in which to locate the second file |
629 * @param baseDir2 the directory in which to locate the second file |
527 * @param file the file to compare in the two base directories |
630 * @param file the file to compare in the two base directories |
528 * @param throwErrorIFNoMatch flag to indicate whether or not to throw |
631 * @param throwErrorIFNoMatch flag to indicate whether or not to throw |
529 * an error if the files do not match. |
632 * an error if the files do not match. |
530 * @return true if the files are the same and false otherwise. |
633 * @return true if the files are the same and false otherwise. |
531 */ |
634 */ |
532 private boolean diff(File baseDir1, File baseDir2, String file, |
635 private void diff(File baseDir1, File baseDir2, String file) { |
533 boolean throwErrorIFNoMatch) throws Error { |
636 String file1Contents = readFile(baseDir1, file); |
534 String file1Contents = readFileToString(baseDir1, file); |
637 String file2Contents = readFile(baseDir2, file); |
535 String file2Contents = readFileToString(baseDir2, file); |
638 checking("diff " + new File(baseDir1, file) + ", " + new File(baseDir2, file)); |
536 numTestsRun++; |
|
537 if (file1Contents.trim().compareTo(file2Contents.trim()) == 0) { |
639 if (file1Contents.trim().compareTo(file2Contents.trim()) == 0) { |
538 System.out.println("Diff successful: " + new File(baseDir1, file) + ", " + new File(baseDir2, file)); |
640 passed("files are equal"); |
539 numTestsPassed++; |
|
540 return true; |
|
541 } else if (throwErrorIFNoMatch) { |
|
542 throw new Error("Diff failed: " + new File(baseDir1, file) + ", " + new File(baseDir2, file)); |
|
543 } else { |
641 } else { |
544 return false; |
642 failed("files differ"); |
545 } |
643 } |
546 } |
644 } |
547 |
645 |
548 /** |
646 /** |
549 * Search for the string in the given file and return true |
647 * Utility class to simplify the handling of temporarily setting a |
550 * if the string was found. |
648 * new stream for System.out or System.err. |
551 * |
649 */ |
552 * @param fileString the contents of the file to search through |
650 private static class StreamOutput { |
553 * @param stringToFind the string to search for |
651 // functional interface to set a stream. |
554 * @return true if the string was found |
652 private interface Initializer { |
555 */ |
653 void set(PrintStream s); |
556 private boolean findString(String fileString, String stringToFind) { |
654 } |
557 return fileString.contains(stringToFind.replace("\n", NL)); |
655 |
558 } |
656 private final ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
559 |
657 private final PrintStream ps = new PrintStream(baos); |
560 |
658 private final PrintStream prev; |
561 /** |
659 private final Initializer init; |
562 * Return the standard output. |
660 |
563 * @return the standard output |
661 StreamOutput(PrintStream s, Initializer init) { |
564 */ |
662 prev = s; |
565 public String getStandardOutput() { |
663 init.set(ps); |
566 return standardOut.toString(); |
664 this.init = init; |
567 } |
665 } |
568 |
666 |
569 /** |
667 String close() { |
570 * Return the error output. |
668 init.set(prev); |
571 * @return the error output |
669 ps.close(); |
572 */ |
670 return baos.toString(); |
573 public String getErrorOutput() { |
671 } |
574 return errors.getBuffer().toString(); |
672 } |
575 } |
673 |
576 |
674 /** |
577 /** |
675 * Utility class to simplify the handling of creating an in-memory PrintWriter. |
578 * Return the notice output. |
676 */ |
579 * @return the notice output |
677 private static class WriterOutput { |
580 */ |
678 private final StringWriter sw = new StringWriter(); |
581 public String getNoticeOutput() { |
679 final PrintWriter pw = new PrintWriter(sw); |
582 return notices.getBuffer().toString(); |
680 String close() { |
583 } |
681 pw.close(); |
584 |
682 return sw.toString(); |
585 /** |
683 } |
586 * Return the warning output. |
684 } |
587 * @return the warning output |
685 |
588 */ |
686 |
589 public String getWarningOutput() { |
687 // private final Logger log = new Logger(); |
590 return warnings.getBuffer().toString(); |
688 |
591 } |
689 //--------- Logging -------------------------------------------------------- |
592 |
690 // |
593 /** |
691 // This class writes out the details of calls to checkOutput and checkFile |
594 * A utility to copy a directory from one place to another. |
692 // in a canonical way, so that the resulting file can be checked against |
595 * We may possibly want to move this to our doclet toolkit in |
693 // similar files from other versions of JavadocTester using the same logging |
596 * the near future and maintain it from there. |
694 // facilities. |
597 * |
695 |
598 * @param targetDir the directory to copy. |
696 static class Logger { |
599 * @param destDir the destination to copy the directory to. |
697 private static final int PREFIX = 40; |
600 */ |
698 private static final int SUFFIX = 20; |
601 public static void copyDir(String targetDir, String destDir) { |
699 private static final int MAX = PREFIX + SUFFIX; |
602 if (targetDir.endsWith("SCCS")) { |
700 List<String> tests = new ArrayList<>(); |
603 return; |
701 String outDir; |
604 } |
702 String rootDir = rootDir(); |
605 try { |
703 |
606 File targetDirObj = new File(targetDir); |
704 static String rootDir() { |
607 File destDirParentObj = new File(destDir); |
705 File f = new File(".").getAbsoluteFile(); |
608 File destDirObj = new File(destDirParentObj, targetDirObj.getName()); |
706 while (!new File(f, ".hg").exists()) |
609 if (! destDirParentObj.exists()) { |
707 f = f.getParentFile(); |
610 destDirParentObj.mkdir(); |
708 return f.getPath(); |
611 } |
709 } |
612 if (! destDirObj.exists()) { |
710 |
613 destDirObj.mkdir(); |
711 void setOutDir(File outDir) { |
614 } |
712 this.outDir = outDir.getPath(); |
615 String[] files = targetDirObj.list(); |
713 } |
616 for (String file : files) { |
714 |
617 File srcFile = new File(targetDirObj, file); |
715 void logCheckFile(String file, boolean positive) { |
618 File destFile = new File(destDirObj, file); |
716 // Strip the outdir because that will typically not be the same |
619 if (srcFile.isFile()) { |
717 if (file.startsWith(outDir + "/")) |
620 System.out.println("Copying " + srcFile + " to " + destFile); |
718 file = file.substring(outDir.length() + 1); |
621 copyFile(destFile, srcFile); |
719 tests.add(file + " " + positive); |
622 } else if(srcFile.isDirectory()) { |
720 } |
623 copyDir(srcFile.getAbsolutePath(), destDirObj.getAbsolutePath()); |
721 |
|
722 void logCheckOutput(String file, boolean positive, String text) { |
|
723 // Compress the string to be displayed in the log file |
|
724 String simpleText = text.replaceAll("\\s+", " ").replace(rootDir, "[ROOT]"); |
|
725 if (simpleText.length() > MAX) |
|
726 simpleText = simpleText.substring(0, PREFIX) |
|
727 + "..." + simpleText.substring(simpleText.length() - SUFFIX); |
|
728 // Strip the outdir because that will typically not be the same |
|
729 if (file.startsWith(outDir + "/")) |
|
730 file = file.substring(outDir.length() + 1); |
|
731 // The use of text.hashCode ensure that all of "text" is taken into account |
|
732 tests.add(file + " " + positive + " " + text.hashCode() + " " + simpleText); |
|
733 } |
|
734 |
|
735 void write() { |
|
736 // sort the log entries because the subtests may not be executed in the same order |
|
737 tests.sort((a, b) -> a.compareTo(b)); |
|
738 try (BufferedWriter bw = new BufferedWriter(new FileWriter("tester.log"))) { |
|
739 for (String t: tests) { |
|
740 bw.write(t); |
|
741 bw.newLine(); |
624 } |
742 } |
625 } |
743 } catch (IOException e) { |
626 } catch (IOException exc) { |
744 throw new Error("problem writing log: " + e); |
627 throw new Error("Could not copy " + targetDir + " to " + destDir); |
745 } |
628 } |
|
629 } |
|
630 |
|
631 /** |
|
632 * Copy source file to destination file. |
|
633 * |
|
634 * @param destfile the destination file |
|
635 * @param srcfile the source file |
|
636 * @throws SecurityException |
|
637 * @throws IOException |
|
638 */ |
|
639 public static void copyFile(File destfile, File srcfile) |
|
640 throws IOException { |
|
641 byte[] bytearr = new byte[512]; |
|
642 int len; |
|
643 FileInputStream input = new FileInputStream(srcfile); |
|
644 File destDir = destfile.getParentFile(); |
|
645 destDir.mkdirs(); |
|
646 FileOutputStream output = new FileOutputStream(destfile); |
|
647 try { |
|
648 while ((len = input.read(bytearr)) != -1) { |
|
649 output.write(bytearr, 0, len); |
|
650 } |
|
651 } catch (FileNotFoundException | SecurityException exc) { |
|
652 } finally { |
|
653 input.close(); |
|
654 output.close(); |
|
655 } |
746 } |
656 } |
747 } |
657 } |
748 } |