1 /* 2 * Copyright (c) 2002, 2017, 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.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.Arrays; 42 import java.util.ArrayList; 43 import java.util.Collection; 44 import java.util.Collections; 45 import java.util.EnumMap; 46 import java.util.HashMap; 47 import java.util.List; 48 import java.util.Map; 49 import java.util.function.Function; 50 51 52 /** 53 * Test framework for running javadoc and performing tests on the resulting output. 54 * 55 * <p> 56 * Tests are typically written as subtypes of JavadocTester, with a main 57 * method that creates an instance of the test class and calls the runTests() 58 * method. The runTests() methods calls all the test methods declared in the class, 59 * and then calls a method to print a summary, and throw an exception if 60 * any of the test methods reported a failure. 61 * 62 * <p> 63 * Test methods are identified with a @Test annotation. They have no parameters. 64 * The name of the method is not important, but if you have more than one, it is 65 * recommended that the names be meaningful and suggestive of the test case 66 * contained therein. 67 * 68 * <p> 69 * Typically, a test method will invoke javadoc, and then perform various 70 * checks on the results. The standard checks are: 71 * 72 * <dl> 73 * <dt>checkExitCode 74 * <dd>Check the exit code returned from javadoc. 75 * <dt>checkOutput 76 * <dd>Perform a series of checks on the contents on a file or output stream 77 * generated by javadoc. 78 * The checks can be either that a series of strings are found or are not found. 79 * <dt>checkFiles 80 * <dd>Perform a series of checks on the files generated by javadoc. 81 * The checks can be that a series of files are found or are not found. 82 * </dl> 83 * 84 * <pre><code> 85 * public class MyTester extends JavadocTester { 86 * public static void main(String... args) throws Exception { 87 * MyTester tester = new MyTester(); 88 * tester.runTests(); 89 * } 90 * 91 * // test methods... 92 * @Test 93 * void test() { 94 * javadoc(<i>args</i>); 95 * checkExit(Exit.OK); 96 * checkOutput(<i>file</i>, true, 97 * <i>strings-to-find</i>); 98 * checkOutput(<i>file</i>, false, 99 * <i>strings-to-not-find</i>); 100 * } 101 * } 102 * </code></pre> 103 * 104 * <p> 105 * If javadoc is run more than once in a test method, you can compare the 106 * results that are generated with the diff method. Since files written by 107 * javadoc typically contain a timestamp, you may want to use the -notimestamp 108 * option if you are going to compare the results from two runs of javadoc. 109 * 110 * <p> 111 * If you have many calls of checkOutput that are very similar, you can write 112 * your own check... method to reduce the amount of duplication. For example, 113 * if you want to check that many files contain the same string, you could 114 * write a method that takes a varargs list of files and calls checkOutput 115 * on each file in turn with the string to be checked. 116 * 117 * <p> 118 * You can also write you own custom check methods, which can use 119 * readFile to get the contents of a file generated by javadoc, 120 * and then use pass(...) or fail(...) to report whether the check 121 * succeeded or not. 122 * 123 * <p> 124 * You can have many separate test methods, each identified with a @Test 125 * annotation. However, you should <b>not</b> assume they will be called 126 * in the order declared in your source file. If the order of a series 127 * of javadoc invocations is important, do that within a single method. 128 * If the invocations are independent, for better clarity, use separate 129 * test methods, each with their own set of checks on the results. 130 * 131 * @author Doug Kramer 132 * @author Jamie Ho 133 * @author Jonathan Gibbons (rewrite) 134 */ 135 public abstract class JavadocTester { 136 137 public static final String FS = System.getProperty("file.separator"); 138 public static final String PS = System.getProperty("path.separator"); 139 public static final String NL = System.getProperty("line.separator"); 140 141 public enum Output { 142 /** The name of the output stream from javadoc. */ 143 OUT, 144 /** The name for any output written to System.out. */ 145 STDOUT, 146 /** The name for any output written to System.err. */ 147 STDERR 148 } 149 150 /** The output directory used in the most recent call of javadoc. */ 151 protected File outputDir; 152 153 /** The exit code of the most recent call of javadoc. */ 154 private int exitCode; 155 156 /** The output generated by javadoc to the various writers and streams. */ 157 private final Map<Output, String> outputMap = new EnumMap<>(Output.class); 158 159 /** A cache of file content, to avoid reading files unnecessarily. */ 160 private final Map<File,SoftReference<String>> fileContentCache = new HashMap<>(); 161 162 /** Stream used for logging messages. */ 163 protected final PrintStream out = System.out; 164 165 /** The directory containing the source code for the test. */ 166 public static final String testSrc = System.getProperty("test.src"); 167 168 /** 169 * Get the path for a source file in the test source directory. 170 * @param path the path of a file or directory in the source directory 171 * @return the full path of the specified file 172 */ 173 public static String testSrc(String path) { 174 return new File(testSrc, path).getPath(); 175 } 176 177 /** 178 * Alternatives for checking the contents of a directory. 179 */ 180 public enum DirectoryCheck { 181 /** 182 * Check that the directory is empty. 183 */ 184 EMPTY((file, name) -> true), 185 /** 186 * Check that the directory does not contain any HTML files, 187 * such as may have been generated by a prior run of javadoc 188 * using this directory. 189 * For now, the check is only performed on the top level directory. 190 */ 191 NO_HTML_FILES((file, name) -> name.endsWith(".html")), 192 /** 193 * No check is performed on the directory contents. 194 */ 195 NONE(null) { @Override void check(File dir) { } }; 196 197 /** The filter used to detect that files should <i>not</i> be present. */ 198 FilenameFilter filter; 199 200 DirectoryCheck(FilenameFilter f) { 201 filter = f; 202 } 203 204 void check(File dir) { 205 if (dir.isDirectory()) { 206 String[] contents = dir.list(filter); 207 if (contents == null) 208 throw new Error("cannot list directory: " + dir); 209 if (contents.length > 0) { 210 System.err.println("Found extraneous files in dir:" + dir.getAbsolutePath()); 211 for (String x : contents) { 212 System.err.println(x); 213 } 214 throw new Error("directory has unexpected content: " + dir); 215 } 216 } 217 } 218 } 219 220 private DirectoryCheck outputDirectoryCheck = DirectoryCheck.EMPTY; 221 222 /** The current subtest number. Incremented when checking(...) is called. */ 223 private int numTestsRun = 0; 224 225 /** The number of subtests passed. Incremented when passed(...) is called. */ 226 private int numTestsPassed = 0; 227 228 /** The current run of javadoc. Incremented when javadoc is called. */ 229 private int javadocRunNum = 0; 230 231 /** Marker annotation for test methods to be invoked by runTests. */ 232 @Retention(RetentionPolicy.RUNTIME) 233 @interface Test { } 234 235 /** 236 * Run all methods annotated with @Test, followed by printSummary. 237 * Typically called on a tester object in main() 238 * @throws Exception if any errors occurred 239 */ 240 public void runTests() throws Exception { 241 runTests(m -> new Object[0]); 242 } 243 244 /** 245 * Run all methods annotated with @Test, followed by printSummary. 246 * Typically called on a tester object in main() 247 * @param f a function which will be used to provide arguments to each 248 * invoked method 249 * @throws Exception if any errors occurred 250 */ 251 public void runTests(Function<Method, Object[]> f) throws Exception { 252 for (Method m: getClass().getDeclaredMethods()) { 253 Annotation a = m.getAnnotation(Test.class); 254 if (a != null) { 255 try { 256 out.println("Running test " + m.getName()); 257 m.invoke(this, f.apply(m)); 258 } catch (InvocationTargetException e) { 259 Throwable cause = e.getCause(); 260 throw (cause instanceof Exception) ? ((Exception) cause) : e; 261 } 262 out.println(); 263 } 264 } 265 printSummary(); 266 } 267 268 /** 269 * Run javadoc. 270 * The output directory used by this call and the final exit code 271 * will be saved for later use. 272 * To aid the reader, it is recommended that calls to this method 273 * put each option and the arguments it takes on a separate line. 274 * 275 * Example: 276 * <pre><code> 277 * javadoc("-d", "out", 278 * "-sourcepath", testSrc, 279 * "-notimestamp", 280 * "pkg1", "pkg2", "pkg3/C.java"); 281 * </code></pre> 282 * 283 * @param args the arguments to pass to javadoc 284 */ 285 public void javadoc(String... args) { 286 outputMap.clear(); 287 fileContentCache.clear(); 288 289 javadocRunNum++; 290 if (javadocRunNum == 1) { 291 out.println("Running javadoc..."); 292 } else { 293 out.println("Running javadoc (run " 294 + javadocRunNum + ")..."); 295 } 296 outputDir = new File("."); 297 for (int i = 0; i < args.length - 2; i++) { 298 if (args[i].equals("-d")) { 299 outputDir = new File(args[++i]); 300 break; 301 } 302 } 303 out.println("args: " + Arrays.toString(args)); 304 // log.setOutDir(outputDir); 305 306 outputDirectoryCheck.check(outputDir); 307 308 // This is the sole stream used by javadoc 309 WriterOutput outOut = new WriterOutput(); 310 311 // These are to catch output to System.out and System.err, 312 // in case these are used instead of the primary streams 313 StreamOutput sysOut = new StreamOutput(System.out, System::setOut); 314 StreamOutput sysErr = new StreamOutput(System.err, System::setErr); 315 316 try { 317 exitCode = jdk.javadoc.internal.tool.Main.execute(args, outOut.pw); 318 } finally { 319 outputMap.put(Output.STDOUT, sysOut.close()); 320 outputMap.put(Output.STDERR, sysErr.close()); 321 outputMap.put(Output.OUT, outOut.close()); 322 } 323 324 outputMap.forEach((name, text) -> { 325 if (!text.isEmpty()) { 326 out.println("javadoc " + name + ":"); 327 out.println(text); 328 } 329 }); 330 } 331 332 /** 333 * Set the kind of check for the initial contents of the output directory 334 * before javadoc is run. 335 * The filter should return true for files that should <b>not</b> appear. 336 * @param c the kind of check to perform 337 */ 338 public void setOutputDirectoryCheck(DirectoryCheck c) { 339 outputDirectoryCheck = c; 340 } 341 342 /** 343 * The exit codes returned by the javadoc tool. 344 * @see jdk.javadoc.internal.tool.Main.Result 345 */ 346 public enum Exit { 347 OK(0), // Javadoc completed with no errors. 348 ERROR(1), // Completed but reported errors. 349 CMDERR(2), // Bad command-line arguments 350 SYSERR(3), // System error or resource exhaustion. 351 ABNORMAL(4); // Javadoc terminated abnormally 352 353 Exit(int code) { 354 this.code = code; 355 } 356 357 final int code; 358 359 @Override 360 public String toString() { 361 return name() + '(' + code + ')'; 362 } 363 } 364 365 /** 366 * Check the exit code of the most recent call of javadoc. 367 * 368 * @param expected the exit code that is required for the test 369 * to pass. 370 */ 371 public void checkExit(Exit expected) { 372 checking("check exit code"); 373 if (exitCode == expected.code) { 374 passed("return code " + exitCode); 375 } else { 376 failed("return code " + exitCode +"; expected " + expected); 377 } 378 } 379 380 /** 381 * Check for content in (or not in) the generated output. 382 * Within the search strings, the newline character \n 383 * will be translated to the platform newline character sequence. 384 * @param path a path within the most recent output directory 385 * or the name of one of the output buffers, identifying 386 * where to look for the search strings. 387 * @param expectedFound true if all of the search strings are expected 388 * to be found, or false if the file is not expected to be found 389 * @param strings the strings to be searched for 390 */ 391 public void checkFileAndOutput(String path, boolean expectedFound, String... strings) { 392 if (expectedFound) { 393 checkOutput(path, true, strings); 394 } else { 395 checkFiles(false, path); 396 } 397 } 398 399 /** 400 * Check for content in (or not in) the generated output. 401 * Within the search strings, the newline character \n 402 * will be translated to the platform newline character sequence. 403 * @param path a path within the most recent output directory 404 * or the name of one of the output buffers, identifying 405 * where to look for the search strings. 406 * @param expectedFound true if all of the search strings are expected 407 * to be found, or false if all of the strings are expected to be 408 * not found 409 * @param strings the strings to be searched for 410 */ 411 public void checkOutput(String path, boolean expectedFound, String... strings) { 412 // Read contents of file 413 String fileString; 414 try { 415 fileString = readFile(outputDir, path); 416 } catch (Error e) { 417 checking("Read file"); 418 failed("Error reading file: " + e); 419 return; 420 } 421 checkOutput(path, fileString, expectedFound, strings); 422 } 423 424 /** 425 * Check for content in (or not in) the one of the output streams written by 426 * javadoc. Within the search strings, the newline character \n 427 * will be translated to the platform newline character sequence. 428 * @param output the output stream to check 429 * @param expectedFound true if all of the search strings are expected 430 * to be found, or false if all of the strings are expected to be 431 * not found 432 * @param strings the strings to be searched for 433 */ 434 public void checkOutput(Output output, boolean expectedFound, String... strings) { 435 checkOutput(output.toString(), outputMap.get(output), expectedFound, strings); 436 } 437 438 private void checkOutput(String path, String fileString, boolean expectedFound, String... strings) { 439 for (String stringToFind : strings) { 440 // log.logCheckOutput(path, expectedFound, stringToFind); 441 checking("checkOutput"); 442 // Find string in file's contents 443 boolean isFound = findString(fileString, stringToFind); 444 if (isFound == expectedFound) { 445 passed(path + ": following text " + (isFound ? "found:" : "not found:") + "\n" 446 + stringToFind + "\n"); 447 } else { 448 failed(path + ": following text " + (isFound ? "found:" : "not found:") + "\n" 449 + stringToFind + "\n"); 450 } 451 } 452 } 453 454 /** 455 * Get the content of the one of the output streams written by 456 * javadoc. 457 */ 458 public String getOutput(Output output) { 459 return outputMap.get(output); 460 } 461 462 /** 463 * Get the content of the one of the output streams written by 464 * javadoc. 465 */ 466 public List<String> getOutputLines(Output output) { 467 String text = outputMap.get(output); 468 return (text == null) ? Collections.emptyList() : Arrays.asList(text.split(NL)); 469 } 470 471 /** 472 * Check for files in (or not in) the generated output. 473 * @param expectedFound true if all of the files are expected 474 * to be found, or false if all of the files are expected to be 475 * not found 476 * @param paths the files to check, within the most recent output directory. 477 * */ 478 public void checkFiles(boolean expectedFound, String... paths) { 479 checkFiles(expectedFound, Arrays.asList(paths)); 480 } 481 482 /** 483 * Check for files in (or not in) the generated output. 484 * @param expectedFound true if all of the files are expected 485 * to be found, or false if all of the files are expected to be 486 * not found 487 * @param paths the files to check, within the most recent output directory. 488 * */ 489 public void checkFiles(boolean expectedFound, Collection<String> paths) { 490 for (String path: paths) { 491 // log.logCheckFile(path, expectedFound); 492 checking("checkFile"); 493 File file = new File(outputDir, path); 494 boolean isFound = file.exists(); 495 if (isFound == expectedFound) { 496 passed(path + ": file " + (isFound ? "found:" : "not found:") + "\n"); 497 } else { 498 failed(path + ": file " + (isFound ? "found:" : "not found:") + "\n"); 499 } 500 } 501 } 502 503 /** 504 * Check that a series of strings are found in order in a file in 505 * the generated output. 506 * @param path the file to check 507 * @param strings the strings whose order to check 508 */ 509 public void checkOrder(String path, String... strings) { 510 String fileString = readOutputFile(path); 511 int prevIndex = -1; 512 for (String s : strings) { 513 s = s.replace("\n", NL); // normalize new lines 514 int currentIndex = fileString.indexOf(s); 515 checking(s + " at index " + currentIndex); 516 if (currentIndex == -1) { 517 failed(s + " not found."); 518 continue; 519 } 520 if (currentIndex > prevIndex) { 521 passed(s + " is in the correct order"); 522 } else { 523 failed("file: " + path + ": " + s + " is in the wrong order."); 524 } 525 prevIndex = currentIndex; 526 } 527 } 528 529 /** 530 * Ensures that a series of strings appear only once, in the generated output, 531 * noting that, this test does not exhaustively check for all other possible 532 * duplicates once one is found. 533 * @param path the file to check 534 * @param strings ensure each are unique 535 */ 536 public void checkUnique(String path, String... strings) { 537 String fileString = readOutputFile(path); 538 for (String s : strings) { 539 int currentIndex = fileString.indexOf(s); 540 checking(s + " at index " + currentIndex); 541 if (currentIndex == -1) { 542 failed(s + " not found."); 543 continue; 544 } 545 int nextindex = fileString.indexOf(s, currentIndex + s.length()); 546 if (nextindex == -1) { 547 passed(s + " is unique"); 548 } else { 549 failed(s + " is not unique, found at " + nextindex); 550 } 551 } 552 } 553 554 /** 555 * Compare a set of files in each of two directories. 556 * 557 * @param baseDir1 the directory containing the first set of files 558 * @param baseDir2 the directory containing the second set of files 559 * @param files the set of files to be compared 560 */ 561 public void diff(String baseDir1, String baseDir2, String... files) { 562 File bd1 = new File(baseDir1); 563 File bd2 = new File(baseDir2); 564 for (String file : files) { 565 diff(bd1, bd2, file); 566 } 567 } 568 569 /** 570 * A utility to copy a directory from one place to another. 571 * 572 * @param targetDir the directory to copy. 573 * @param destDir the destination to copy the directory to. 574 */ 575 // TODO: convert to using java.nio.Files.walkFileTree 576 public void copyDir(String targetDir, String destDir) { 577 try { 578 File targetDirObj = new File(targetDir); 579 File destDirParentObj = new File(destDir); 580 File destDirObj = new File(destDirParentObj, targetDirObj.getName()); 581 if (! destDirParentObj.exists()) { 582 destDirParentObj.mkdir(); 583 } 584 if (! destDirObj.exists()) { 585 destDirObj.mkdir(); 586 } 587 String[] files = targetDirObj.list(); 588 for (String file : files) { 589 File srcFile = new File(targetDirObj, file); 590 File destFile = new File(destDirObj, file); 591 if (srcFile.isFile()) { 592 out.println("Copying " + srcFile + " to " + destFile); 593 copyFile(destFile, srcFile); 594 } else if(srcFile.isDirectory()) { 595 copyDir(srcFile.getAbsolutePath(), destDirObj.getAbsolutePath()); 596 } 597 } 598 } catch (IOException exc) { 599 throw new Error("Could not copy " + targetDir + " to " + destDir); 600 } 601 } 602 603 /** 604 * Copy source file to destination file. 605 * 606 * @param destfile the destination file 607 * @param srcfile the source file 608 * @throws IOException 609 */ 610 public void copyFile(File destfile, File srcfile) throws IOException { 611 Files.copy(srcfile.toPath(), destfile.toPath()); 612 } 613 614 /** 615 * Read a file from the output directory. 616 * 617 * @param fileName the name of the file to read 618 * @return the file in string format 619 */ 620 public String readOutputFile(String fileName) throws Error { 621 return readFile(outputDir, fileName); 622 } 623 624 protected String readFile(String fileName) throws Error { 625 return readFile(outputDir, fileName); 626 } 627 628 protected String readFile(String baseDir, String fileName) throws Error { 629 return readFile(new File(baseDir), fileName); 630 } 631 632 /** 633 * Read the file and return it as a string. 634 * 635 * @param baseDir the directory in which to locate the file 636 * @param fileName the name of the file to read 637 * @return the file in string format 638 */ 639 private String readFile(File baseDir, String fileName) throws Error { 640 try { 641 File file = new File(baseDir, fileName); 642 SoftReference<String> ref = fileContentCache.get(file); 643 String content = (ref == null) ? null : ref.get(); 644 if (content != null) 645 return content; 646 647 content = new String(Files.readAllBytes(file.toPath())); 648 fileContentCache.put(file, new SoftReference<>(content)); 649 return content; 650 } catch (FileNotFoundException e) { 651 throw new Error("File not found: " + fileName + ": " + e); 652 } catch (IOException e) { 653 throw new Error("Error reading file: " + fileName + ": " + e); 654 } 655 } 656 657 protected void checking(String message) { 658 numTestsRun++; 659 print("Starting subtest " + numTestsRun, message); 660 } 661 662 protected void passed(String message) { 663 numTestsPassed++; 664 print("Passed", message); 665 } 666 667 protected void failed(String message) { 668 print("FAILED", message); 669 } 670 671 private void print(String prefix, String message) { 672 if (message.isEmpty()) 673 out.println(prefix); 674 else { 675 out.print(prefix); 676 out.print(": "); 677 out.println(message.replace("\n", NL)); 678 } 679 } 680 681 /** 682 * Print a summary of the test results. 683 */ 684 protected void printSummary() { 685 String javadocRuns = (javadocRunNum <= 1) ? "" 686 : ", in " + javadocRunNum + " runs of javadoc"; 687 688 if (numTestsRun != 0 && numTestsPassed == numTestsRun) { 689 // Test passed 690 out.println(); 691 out.println("All " + numTestsPassed + " subtests passed" + javadocRuns); 692 } else { 693 // Test failed 694 throw new Error((numTestsRun - numTestsPassed) 695 + " of " + (numTestsRun) 696 + " subtests failed" 697 + javadocRuns); 698 } 699 } 700 701 /** 702 * Search for the string in the given file and return true 703 * if the string was found. 704 * 705 * @param fileString the contents of the file to search through 706 * @param stringToFind the string to search for 707 * @return true if the string was found 708 */ 709 private boolean findString(String fileString, String stringToFind) { 710 // javadoc (should) always use the platform newline sequence, 711 // but in the strings to find it is more convenient to use the Java 712 // newline character. So we translate \n to NL before we search. 713 stringToFind = stringToFind.replace("\n", NL); 714 return fileString.contains(stringToFind); 715 } 716 717 /** 718 * Compare the two given files. 719 * 720 * @param baseDir1 the directory in which to locate the first file 721 * @param baseDir2 the directory in which to locate the second file 722 * @param file the file to compare in the two base directories 723 * @param throwErrorIFNoMatch flag to indicate whether or not to throw 724 * an error if the files do not match. 725 * @return true if the files are the same and false otherwise. 726 */ 727 private void diff(File baseDir1, File baseDir2, String file) { 728 String file1Contents = readFile(baseDir1, file); 729 String file2Contents = readFile(baseDir2, file); 730 checking("diff " + new File(baseDir1, file) + ", " + new File(baseDir2, file)); 731 if (file1Contents.trim().compareTo(file2Contents.trim()) == 0) { 732 passed("files are equal"); 733 } else { 734 failed("files differ"); 735 } 736 } 737 738 /** 739 * Utility class to simplify the handling of temporarily setting a 740 * new stream for System.out or System.err. 741 */ 742 private static class StreamOutput { 743 // functional interface to set a stream. 744 private interface Initializer { 745 void set(PrintStream s); 746 } 747 748 private final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 749 private final PrintStream ps = new PrintStream(baos); 750 private final PrintStream prev; 751 private final Initializer init; 752 753 StreamOutput(PrintStream s, Initializer init) { 754 prev = s; 755 init.set(ps); 756 this.init = init; 757 } 758 759 String close() { 760 init.set(prev); 761 ps.close(); 762 return baos.toString(); 763 } 764 } 765 766 /** 767 * Utility class to simplify the handling of creating an in-memory PrintWriter. 768 */ 769 private static class WriterOutput { 770 private final StringWriter sw = new StringWriter(); 771 final PrintWriter pw = new PrintWriter(sw); 772 String close() { 773 pw.close(); 774 return sw.toString(); 775 } 776 } 777 778 779 // private final Logger log = new Logger(); 780 781 //--------- Logging -------------------------------------------------------- 782 // 783 // This class writes out the details of calls to checkOutput and checkFile 784 // in a canonical way, so that the resulting file can be checked against 785 // similar files from other versions of JavadocTester using the same logging 786 // facilities. 787 788 static class Logger { 789 private static final int PREFIX = 40; 790 private static final int SUFFIX = 20; 791 private static final int MAX = PREFIX + SUFFIX; 792 List<String> tests = new ArrayList<>(); 793 String outDir; 794 String rootDir = rootDir(); 795 796 static String rootDir() { 797 File f = new File(".").getAbsoluteFile(); 798 while (!new File(f, ".hg").exists()) 799 f = f.getParentFile(); 800 return f.getPath(); 801 } 802 803 void setOutDir(File outDir) { 804 this.outDir = outDir.getPath(); 805 } 806 807 void logCheckFile(String file, boolean positive) { 808 // Strip the outdir because that will typically not be the same 809 if (file.startsWith(outDir + "/")) 810 file = file.substring(outDir.length() + 1); 811 tests.add(file + " " + positive); 812 } 813 814 void logCheckOutput(String file, boolean positive, String text) { 815 // Compress the string to be displayed in the log file 816 String simpleText = text.replaceAll("\\s+", " ").replace(rootDir, "[ROOT]"); 817 if (simpleText.length() > MAX) 818 simpleText = simpleText.substring(0, PREFIX) 819 + "..." + simpleText.substring(simpleText.length() - SUFFIX); 820 // Strip the outdir because that will typically not be the same 821 if (file.startsWith(outDir + "/")) 822 file = file.substring(outDir.length() + 1); 823 // The use of text.hashCode ensure that all of "text" is taken into account 824 tests.add(file + " " + positive + " " + text.hashCode() + " " + simpleText); 825 } 826 827 void write() { 828 // sort the log entries because the subtests may not be executed in the same order 829 tests.sort((a, b) -> a.compareTo(b)); 830 try (BufferedWriter bw = new BufferedWriter(new FileWriter("tester.log"))) { 831 for (String t: tests) { 832 bw.write(t); 833 bw.newLine(); 834 } 835 } catch (IOException e) { 836 throw new Error("problem writing log: " + e); 837 } 838 } 839 } 840 }